🐛 fix BSON conformance issue

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-05-19 14:32:01 +02:00
parent 530ab84ed6
commit c78d9dc386
3 changed files with 67 additions and 17 deletions
@@ -170,7 +170,10 @@ class binary_reader
bool parse_bson_internal() bool parse_bson_internal()
{ {
std::int32_t document_size{}; std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(input_format_t::bson, document_size))))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(document_size < 5)) if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{ {
@@ -181,9 +184,10 @@ class binary_reader
"document size"), nullptr)); "document size"), nullptr));
} }
// chars_read now points just past the 4-byte size field; // The document begins at the size field and ends document_size bytes later
// the document started 4 bytes earlier and must end at start + document_size. // (including the size field itself and the trailing 0x00 terminator).
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4; const std::size_t document_start = chars_read - sizeof(std::int32_t);
const std::size_t expected_end = document_start + static_cast<std::size_t>(document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{ {
@@ -434,7 +438,10 @@ class binary_reader
bool parse_bson_array() bool parse_bson_array()
{ {
std::int32_t document_size{}; std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(input_format_t::bson, document_size))))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(document_size < 5)) if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{ {
@@ -445,9 +452,10 @@ class binary_reader
"document size"), nullptr)); "document size"), nullptr));
} }
// chars_read now points just past the 4-byte size field; // The document begins at the size field and ends document_size bytes later
// the document started 4 bytes earlier and must end at start + document_size. // (including the size field itself and the trailing 0x00 terminator).
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4; const std::size_t document_start = chars_read - sizeof(std::int32_t);
const std::size_t expected_end = document_start + static_cast<std::size_t>(document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{ {
+16 -8
View File
@@ -10312,7 +10312,10 @@ class binary_reader
bool parse_bson_internal() bool parse_bson_internal()
{ {
std::int32_t document_size{}; std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(input_format_t::bson, document_size))))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(document_size < 5)) if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{ {
@@ -10323,9 +10326,10 @@ class binary_reader
"document size"), nullptr)); "document size"), nullptr));
} }
// chars_read now points just past the 4-byte size field; // The document begins at the size field and ends document_size bytes later
// the document started 4 bytes earlier and must end at start + document_size. // (including the size field itself and the trailing 0x00 terminator).
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4; const std::size_t document_start = chars_read - sizeof(std::int32_t);
const std::size_t expected_end = document_start + static_cast<std::size_t>(document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{ {
@@ -10576,7 +10580,10 @@ class binary_reader
bool parse_bson_array() bool parse_bson_array()
{ {
std::int32_t document_size{}; std::int32_t document_size{};
get_number<std::int32_t, true>(input_format_t::bson, document_size); if (JSON_HEDLEY_UNLIKELY((!get_number<std::int32_t, true>(input_format_t::bson, document_size))))
{
return false;
}
if (JSON_HEDLEY_UNLIKELY(document_size < 5)) if (JSON_HEDLEY_UNLIKELY(document_size < 5))
{ {
@@ -10587,9 +10594,10 @@ class binary_reader
"document size"), nullptr)); "document size"), nullptr));
} }
// chars_read now points just past the 4-byte size field; // The document begins at the size field and ends document_size bytes later
// the document started 4 bytes earlier and must end at start + document_size. // (including the size field itself and the trailing 0x00 terminator).
const std::size_t expected_end = chars_read + static_cast<std::size_t>(document_size) - 4; const std::size_t document_start = chars_read - sizeof(std::int32_t);
const std::size_t expected_end = document_start + static_cast<std::size_t>(document_size);
if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{ {
+35 -1
View File
@@ -1302,6 +1302,7 @@ TEST_CASE("Invalid document size handling")
std::vector<std::uint8_t> const v = {0x04, 0x00, 0x00, 0x00, 0x00}; std::vector<std::uint8_t> const v = {0x04, 0x00, 0x00, 0x00, 0x00};
json _; 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&); 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&);
CHECK(json::from_bson(v, true, false).is_discarded());
} }
SECTION("declared document size must match consumed bytes (extra trailing element)") SECTION("declared document size must match consumed bytes (extra trailing element)")
@@ -1316,6 +1317,38 @@ TEST_CASE("Invalid document size handling")
}; };
json _; 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&); 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&);
CHECK(json::from_bson(v, true, false).is_discarded());
}
SECTION("declared document size must match consumed bytes (premature terminator)")
{
// Declares 32-byte document but only contains the size field followed by an immediate terminator.
std::vector<std::uint8_t> const v =
{
0x20, 0x00, 0x00, 0x00,
0x00
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 5: syntax error while parsing BSON document: BSON document terminator did not land at declared document size", json::parse_error&);
CHECK(json::from_bson(v, true, false).is_discarded());
}
SECTION("array declared size must match consumed bytes")
{
// Outer object contains an array "a" that declares 5 bytes (empty) but
// actually contains an int32 element before its terminator.
std::vector<std::uint8_t> const v =
{
0x14, 0x00, 0x00, 0x00, // object size = 20
0x04, 'a', 0x00, // key "a", array type
0x05, 0x00, 0x00, 0x00, // array declared size = 5 (empty)
0x10, '0', 0x00, 0x01, 0x00, 0x00, 0x00, // extra int32 element "0" = 1
0x00, // array terminator
0x00 // object terminator
};
json _;
CHECK_THROWS_WITH_AS(_ = json::from_bson(v), "[json.exception.parse_error.112] parse error at byte 19: syntax error while parsing BSON document: BSON document terminator did not land at declared document size", json::parse_error&);
CHECK(json::from_bson(v, true, false).is_discarded());
} }
SECTION("BSON string must end with 0x00") SECTION("BSON string must end with 0x00")
@@ -1331,5 +1364,6 @@ TEST_CASE("Invalid document size handling")
}; };
json _; 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&); 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&);
CHECK(json::from_bson(v, true, false).is_discarded());
} }
} }