Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 69 additions & 3 deletions include/nlohmann/detail/input/binary_reader.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,24 @@ class binary_reader
bool parse_bson_internal()
{
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))
{
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));
}

// 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<std::size_t>(document_size);

if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
Expand All @@ -182,6 +199,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();
}

Expand Down Expand Up @@ -231,7 +257,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;
}

/*!
Expand Down Expand Up @@ -398,7 +438,24 @@ class binary_reader
bool parse_bson_array()
{
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))
{
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));
}

// 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<std::size_t>(document_size);

if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
Expand All @@ -410,6 +467,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 array terminator did not land at declared array size",
"array"), nullptr));
}

return sax->end_array();
}

Expand Down
72 changes: 69 additions & 3 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10312,7 +10312,24 @@ class binary_reader
bool parse_bson_internal()
{
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))
{
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));
}

// 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<std::size_t>(document_size);

if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))
{
Expand All @@ -10324,6 +10341,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();
}

Expand Down Expand Up @@ -10373,7 +10399,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;
}

/*!
Expand Down Expand Up @@ -10540,7 +10580,24 @@ class binary_reader
bool parse_bson_array()
{
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))
{
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));
}

// 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<std::size_t>(document_size);

if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))
{
Expand All @@ -10552,6 +10609,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 array terminator did not land at declared array size",
"array"), nullptr));
}

return sax->end_array();
}

Expand Down
73 changes: 73 additions & 0 deletions tests/src/unit-bson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1294,3 +1294,76 @@ 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&);
CHECK(json::from_bson(v, true, false).is_discarded());
}

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&);
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 array: BSON array terminator did not land at declared array size", json::parse_error&);
CHECK(json::from_bson(v, true, false).is_discarded());
}

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&);
CHECK(json::from_bson(v, true, false).is_discarded());
}
}
18 changes: 10 additions & 8 deletions tests/src/unit-serialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)()
}
};
Comment on lines +314 to +323
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This is an unrelated whitespace change that was done by astyle.


for (long double v : values)
{
Expand Down
Loading