diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 2a89d2b66..f4df800c2 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -170,7 +170,10 @@ class binary_reader bool parse_bson_internal() { std::int32_t document_size{}; - get_number(input_format_t::bson, document_size); + if (JSON_HEDLEY_UNLIKELY((!get_number(input_format_t::bson, document_size)))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(document_size < 5)) { @@ -181,9 +184,10 @@ class binary_reader "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(document_size) - 4; + // The document begins at the size field and ends document_size bytes later + // (including the size field itself and the trailing 0x00 terminator). + const std::size_t document_start = chars_read - sizeof(std::int32_t); + const std::size_t expected_end = document_start + static_cast(document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { @@ -434,7 +438,10 @@ class binary_reader bool parse_bson_array() { std::int32_t document_size{}; - get_number(input_format_t::bson, document_size); + if (JSON_HEDLEY_UNLIKELY((!get_number(input_format_t::bson, document_size)))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(document_size < 5)) { @@ -445,9 +452,10 @@ class binary_reader "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(document_size) - 4; + // The document begins at the size field and ends document_size bytes later + // (including the size field itself and the trailing 0x00 terminator). + const std::size_t document_start = chars_read - sizeof(std::int32_t); + const std::size_t expected_end = document_start + static_cast(document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 406df31e5..3d269799e 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10312,7 +10312,10 @@ class binary_reader bool parse_bson_internal() { std::int32_t document_size{}; - get_number(input_format_t::bson, document_size); + if (JSON_HEDLEY_UNLIKELY((!get_number(input_format_t::bson, document_size)))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(document_size < 5)) { @@ -10323,9 +10326,10 @@ class binary_reader "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(document_size) - 4; + // The document begins at the size field and ends document_size bytes later + // (including the size field itself and the trailing 0x00 terminator). + const std::size_t document_start = chars_read - sizeof(std::int32_t); + const std::size_t expected_end = document_start + static_cast(document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size()))) { @@ -10576,7 +10580,10 @@ class binary_reader bool parse_bson_array() { std::int32_t document_size{}; - get_number(input_format_t::bson, document_size); + if (JSON_HEDLEY_UNLIKELY((!get_number(input_format_t::bson, document_size)))) + { + return false; + } if (JSON_HEDLEY_UNLIKELY(document_size < 5)) { @@ -10587,9 +10594,10 @@ class binary_reader "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(document_size) - 4; + // The document begins at the size field and ends document_size bytes later + // (including the size field itself and the trailing 0x00 terminator). + const std::size_t document_start = chars_read - sizeof(std::int32_t); + const std::size_t expected_end = document_start + static_cast(document_size); if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size()))) { diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index b5cd0041f..53c308a23 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -1302,6 +1302,7 @@ TEST_CASE("Invalid document size handling") std::vector 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&); + CHECK(json::from_bson(v, true, false).is_discarded()); } SECTION("declared document size must match consumed bytes (extra trailing element)") @@ -1316,6 +1317,38 @@ TEST_CASE("Invalid document size handling") }; 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(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 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 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") @@ -1331,5 +1364,6 @@ TEST_CASE("Invalid document size handling") }; 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(json::from_bson(v, true, false).is_discarded()); } -} \ No newline at end of file +}