Reserve capped array capacity for definite-length binary arrays

CBOR, MessagePack, and the optimized [$type#count UBJSON/BJData form all
pass an exact element count to sax->start_array(len), but
json_sax_dom_parser::start_array() (and the callback variant) only used
len for an overflow check against max_size() and never reserved the
underlying vector, so each element triggered a reallocation cascade via
emplace_back().

Reserve upfront, but cap the reservation at 16384 elements: max_size()
for a std::vector is far larger than any realistic input, so an
unbounded reserve(len) would let a crafted/truncated header (e.g. CBOR
0x9A + a huge uint32 count with no data) trigger a multi-gigabyte
allocation attempt instead of the normal graceful parse_error. With the
cap, a hostile length still fails fast with the existing parse_error,
while realistic arrays get a single up-front allocation.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-09-05 20:51:47 +02:00
parent 09b6b6b5ba
commit aee9421883
6 changed files with 266 additions and 0 deletions
+20
View File
@@ -9829,6 +9829,16 @@ class json_sax_dom_parser
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
}
if (len != detail::unknown_size())
{
// reserve upfront to avoid repeated reallocations while adding elements,
// but cap the reservation so a bogus/hostile length (which is not bounded
// by max_size(), unlike e.g. std::vector) cannot trigger an oversized
// allocation for a small or truncated input
constexpr std::size_t reserve_cap = 16384;
ref_stack.back()->m_data.m_value.array->reserve(len < reserve_cap ? len : reserve_cap);
}
return true;
}
@@ -10189,6 +10199,16 @@ class json_sax_dom_callback_parser
{
JSON_THROW(out_of_range::create(408, concat("excessive array size: ", std::to_string(len)), ref_stack.back()));
}
if (len != detail::unknown_size())
{
// reserve upfront to avoid repeated reallocations while adding elements,
// but cap the reservation so a bogus/hostile length (which is not bounded
// by max_size(), unlike e.g. std::vector) cannot trigger an oversized
// allocation for a small or truncated input
constexpr std::size_t reserve_cap = 16384;
ref_stack.back()->m_data.m_value.array->reserve(len < reserve_cap ? len : reserve_cap);
}
}
return true;