diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 033cfebd7..2a89d2b66 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -172,6 +172,19 @@ class binary_reader std::int32_t document_size{}; get_number(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(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(1), result) && get() != char_traits::eof(); + if (JSON_HEDLEY_UNLIKELY(!get_string(input_format_t::bson, len - static_cast(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(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(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(); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 2e16ad5b9..406df31e5 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10314,6 +10314,19 @@ class binary_reader std::int32_t document_size{}; get_number(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(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(1), result) && get() != char_traits::eof(); + if (JSON_HEDLEY_UNLIKELY(!get_string(input_format_t::bson, len - static_cast(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(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(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(); } diff --git a/tests/src/unit-bson.cpp b/tests/src/unit-bson.cpp index eed70352e..b5cd0041f 100644 --- a/tests/src/unit-bson.cpp +++ b/tests/src/unit-bson.cpp @@ -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 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 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 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&); + } +} \ No newline at end of file diff --git a/tests/src/unit-serialization.cpp b/tests/src/unit-serialization.cpp index c0f2a8e04..f55ed8470 100644 --- a/tests/src/unit-serialization.cpp +++ b/tests/src/unit-serialization.cpp @@ -311,14 +311,16 @@ TEST_CASE("dump for basic_json with long double number_float_t") SECTION("round-trip dump/parse") { constexpr std::array 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::min)(), - std::numeric_limits::lowest(), - (std::numeric_limits::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::min)(), + std::numeric_limits::lowest(), + (std::numeric_limits::max)() + } + }; for (long double v : values) {