🐛 fix BSON conformance issue

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-05-19 14:18:55 +02:00
parent d96329f8d6
commit 530ab84ed6
4 changed files with 167 additions and 10 deletions
@@ -172,6 +172,19 @@ class binary_reader
std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
concat("BSON document size must be at least 5, is ", std::to_string(document_size)),
"document size"), nullptr));
}
// chars_read now points just past the 4-byte size field;
// the document started 4 bytes earlier and must end at start + document_size.
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4;
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
return false;
@@ -182,6 +195,15 @@ class binary_reader
return false;
}
if (JSON_HEDLEY_UNLIKELY(chars_read != expected_end))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
"BSON document terminator did not land at declared document size",
"document"), nullptr));
}
return sax->end_object();
}
@@ -231,7 +253,21 @@ class binary_reader
exception_message(input_format_t::bson, concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr));
}
return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof();
if (JSON_HEDLEY_UNLIKELY(!get_string(input_format_t::bson, len - static_cast<NumberType>(1), result)))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(get() != 0x00))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
"BSON string is not null-terminated",
"string"), nullptr));
}
return true;
}
/*!
@@ -400,6 +436,19 @@ class binary_reader
std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
concat("BSON document size must be at least 5, is ", std::to_string(document_size)),
"document size"), nullptr));
}
// chars_read now points just past the 4-byte size field;
// the document started 4 bytes earlier and must end at start + document_size.
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4;
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
return false;
@@ -410,6 +459,15 @@ class binary_reader
return false;
}
if (JSON_HEDLEY_UNLIKELY(chars_read != expected_end))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
"BSON document terminator did not land at declared document size",
"document"), nullptr));
}
return sax->end_array();
}
+59 -1
View File
@@ -10314,6 +10314,19 @@ class binary_reader
std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
concat("BSON document size must be at least 5, is ", std::to_string(document_size)),
"document size"), nullptr));
}
// chars_read now points just past the 4-byte size field;
// the document started 4 bytes earlier and must end at start + document_size.
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4;
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
return false;
@@ -10324,6 +10337,15 @@ class binary_reader
return false;
}
if (JSON_HEDLEY_UNLIKELY(chars_read != expected_end))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
"BSON document terminator did not land at declared document size",
"document"), nullptr));
}
return sax->end_object();
}
@@ -10373,7 +10395,21 @@ class binary_reader
exception_message(input_format_t::bson, concat("string length must be at least 1, is ", std::to_string(len)), "string"), nullptr));
}
return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof();
if (JSON_HEDLEY_UNLIKELY(!get_string(input_format_t::bson, len - static_cast<NumberType>(1), result)))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(get() != 0x00))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
"BSON string is not null-terminated",
"string"), nullptr));
}
return true;
}
/*!
@@ -10542,6 +10578,19 @@ class binary_reader
std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size);
if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
concat("BSON document size must be at least 5, is ", std::to_string(document_size)),
"document size"), nullptr));
}
// chars_read now points just past the 4-byte size field;
// the document started 4 bytes earlier and must end at start + document_size.
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4;
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
return false;
@@ -10552,6 +10601,15 @@ class binary_reader
return false;
}
if (JSON_HEDLEY_UNLIKELY(chars_read != expected_end))
{
auto last_token = get_token_string();
return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,
exception_message(input_format_t::bson,
"BSON document terminator did not land at declared document size",
"document"), nullptr));
}
return sax->end_array();
}
+39
View File
@@ -1294,3 +1294,42 @@ TEST_CASE("BSON roundtrips" * doctest::skip())
}
}
}
TEST_CASE("Invalid document size handling")
{
SECTION("document size must be at least 5")
{
std::vector<std::uint8_t> const v = {0x04, 0x00, 0x00, 0x00, 0x00};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 4: syntax error while parsing BSON document size: BSON document size must be at least 5, is 4", json::parse_error&);
}
SECTION("declared document size must match consumed bytes (extra trailing element)")
{
// Declares 5-byte empty document but appends an int32 element after the declared end.
std::vector<std::uint8_t> const v =
{
0x05, 0x00, 0x00, 0x00,
0x10, 'a', 'd', 'm', 'i', 'n', 0x00,
0x01, 0x00, 0x00, 0x00,
0x00
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 16: syntax error while parsing BSON document: BSON document terminator did not land at declared document size", json::parse_error&);
}
SECTION("BSON string must end with 0x00")
{
// Length-prefixed string whose terminator byte is 'X' (0x58), not 0x00.
std::vector<std::uint8_t> const v =
{
0x0F, 0x00, 0x00, 0x00,
0x02, 's', 0x00,
0x02, 0x00, 0x00, 0x00,
'A', 'X',
0x00
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 13: syntax error while parsing BSON string: BSON string is not null-terminated", json::parse_error&);
}
}
+10 -8
View File
@@ -311,14 +311,16 @@ TEST_CASE("dump for basic_json with long double number_float_t")
SECTION("round-trip dump/parse")
{
constexpr std::array<long double, 13> values =
{{
0.0L, -0.0L, 1.0L, -1.0L,
0.5L, -0.5L, 1.5L, -2.25L,
1.23e45L, 1.23e-45L,
(std::numeric_limits<long double>::min)(),
std::numeric_limits<long double>::lowest(),
(std::numeric_limits<long double>::max)()
}};
{
{
0.0L, -0.0L, 1.0L, -1.0L,
0.5L, -0.5L, 1.5L, -2.25L,
1.23e45L, 1.23e-45L,
(std::numeric_limits<long double>::min)(),
std::numeric_limits<long double>::lowest(),
(std::numeric_limits<long double>::max)()
}
};
for (long double v : values)
{