mirror of
https://github.com/nlohmann/json.git
synced 2026-10-10 08:27:13 +00:00
Check the node array size for overflow
reserve(n) and the growth of the index computed n * sizeof(node) without a check, which wraps around on 32-bit targets for inputs of about 1 GiB and allocates a too small array. Throw std::bad_alloc for a count beyond the address space and clamp the growth step to it. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+62
-4
No files matched your search
@@ -9,7 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm> // find, find_if, max
|
||||
#include <algorithm> // find, find_if, max, min
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t, ptrdiff_t
|
||||
#include <cstdint> // int64_t, uint8_t, uint16_t, uint32_t, uint64_t
|
||||
@@ -195,8 +195,18 @@ class builder
|
||||
const std::uint64_t done = static_cast<std::uint64_t>(at - b) + 1;
|
||||
const std::uint64_t guess = static_cast<std::uint64_t>(n) * static_cast<std::uint64_t>(e - b + 1) / done;
|
||||
const std::uint64_t grown = guess + (guess / 4) + 64; // a variable: GCC calls a cast of the sum useless where std::uint64_t is std::size_t
|
||||
// (n is below 2^32: the input is smaller than 4 GiB; the sum cannot wrap)
|
||||
const std::uint64_t wanted = (std::max)(grown, static_cast<std::uint64_t>(n) + (n / 2) + 64);
|
||||
const std::uint64_t limit = document_data::max_nodes();
|
||||
doc.tape_size = n;
|
||||
doc.reserve((std::max)(static_cast<std::size_t>(grown), n + (n / 2) + 64));
|
||||
// LCOV_EXCL_START (a node array that fills the address space)
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n >= limit))
|
||||
{
|
||||
document_data::throw_bad_alloc(); // no room for another node
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
// (a count beyond the limit is cut: the index does not grow beyond what can be addressed)
|
||||
doc.reserve(static_cast<std::size_t>((std::min)(wanted, limit)));
|
||||
return doc.tape;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
#include <array> // array
|
||||
#include <cstddef> // size_t
|
||||
#include <cstring> // memcpy
|
||||
#include <new> // operator new, placement new
|
||||
#include <limits> // numeric_limits
|
||||
#include <new> // bad_alloc, operator new, placement new
|
||||
#include <string> // string
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
@@ -84,13 +85,30 @@ struct document_data
|
||||
tape_cap = inline_cap;
|
||||
}
|
||||
|
||||
/// make room for n nodes; keeps the first tape_size nodes
|
||||
/// the largest node count whose size in bytes fits a std::size_t
|
||||
static constexpr std::size_t max_nodes() noexcept
|
||||
{
|
||||
return (std::numeric_limits<std::size_t>::max)() / sizeof(node);
|
||||
}
|
||||
|
||||
[[noreturn]] NLOHMANN_VIEW_NOINLINE static void throw_bad_alloc()
|
||||
{
|
||||
NLOHMANN_VIEW_THROW(std::bad_alloc());
|
||||
}
|
||||
|
||||
/// make room for n nodes; keeps the first tape_size nodes (throws
|
||||
/// std::bad_alloc for a count that does not fit the address space,
|
||||
/// instead of wrapping around in n * sizeof(node))
|
||||
void reserve(std::size_t n)
|
||||
{
|
||||
if (n <= tape_cap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (NLOHMANN_VIEW_UNLIKELY(n > max_nodes()))
|
||||
{
|
||||
throw_bad_alloc();
|
||||
}
|
||||
node* fresh = static_cast<node*>(::operator new (n * sizeof(node)));
|
||||
if (tape_size != 0)
|
||||
{
|
||||
|
||||
@@ -19,8 +19,10 @@ using nlohmann::json;
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
@@ -459,3 +461,31 @@ TEST_CASE("json_view node integer bits")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("json_view node array size limit")
|
||||
{
|
||||
// a node array larger than the address space is refused, not wrapped to a
|
||||
// small allocation (the size computation overflows on 32-bit targets, and
|
||||
// for absurd counts everywhere)
|
||||
std::unique_ptr<document_data, document_data::deleter> d(document_data::create(0));
|
||||
d->reserve(8);
|
||||
REQUIRE(d->tape_cap >= 8);
|
||||
d->tape_size = 2;
|
||||
const std::size_t cap = d->tape_cap;
|
||||
node* const tape = d->tape;
|
||||
|
||||
#if !defined(JSON_NOEXCEPTION)
|
||||
const std::size_t too_many = document_data::max_nodes() + 1;
|
||||
CHECK_THROWS_AS(d->reserve(too_many), std::bad_alloc&);
|
||||
CHECK_THROWS_AS(d->reserve((std::numeric_limits<std::size_t>::max)()), std::bad_alloc&);
|
||||
// the array is unchanged
|
||||
CHECK(d->tape == tape);
|
||||
CHECK(d->tape_cap == cap);
|
||||
CHECK(d->tape_size == 2);
|
||||
#endif
|
||||
|
||||
// the largest count that fits is not refused by the check (nothing is
|
||||
// allocated for a count that is already there)
|
||||
d->reserve(cap);
|
||||
CHECK(d->tape == tape);
|
||||
}
|
||||
Reference in new issue
Block a user