Skip to content
Merged
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
3 changes: 3 additions & 0 deletions docs/mkdocs/docs/api/basic_json/to_bson.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ Strong guarantee: if an exception is thrown, there are no changes in the JSON va
is not an object; example: `"to serialize to BSON, top-level type must be object, but is string"`
- Throws [`out_of_range.409`](../../home/exceptions.md#jsonexceptionout_of_range409) if a key in the JSON object contains
a null byte (code point U+0000); example: `"BSON key cannot contain code point U+0000 (at byte 2)"`
- Throws [`out_of_range.412`](../../home/exceptions.md#jsonexceptionout_of_range412) if the length of a document, array,
string, or binary value exceeds the range of the 32-bit BSON length field; example:
`"BSON length 2147483661 exceeds maximum of 2147483647"`

## Complexity

Expand Down
16 changes: 16 additions & 0 deletions docs/mkdocs/docs/home/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,22 @@ A JSON Patch `add` operation cannot be applied because the target location's par

This exception was added in version 3.13.0. Before that, this situation hit an internal assertion (aborting the program in debug builds) or was silently ignored when assertions were disabled.

### json.exception.out_of_range.412

BSON stores the length of documents, arrays, strings, and binary values in a signed 32-bit integer. This exception is thrown when a value is too large to be described by such a length field.

!!! failure "Example message"

```
BSON length 2147483661 exceeds maximum of 2147483647
```

!!! note

This exception was added in version 3.13.0. Before that, the length was silently truncated, and
[`to_bson`](../api/basic_json/to_bson.md) produced documents with negative length prefixes that
[`from_bson`](../api/basic_json/from_bson.md) rejected.

## Further exceptions

This exception is thrown in case of errors that cannot be classified with the
Expand Down
23 changes: 19 additions & 4 deletions include/nlohmann/detail/output/binary_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,21 @@ class binary_writer
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
}

/*!
@brief Checks that @a size fits into the 32-bit length field used by BSON
@return The size as a signed 32-bit integer
@throw out_of_range.412 if @a size exceeds the range of std::int32_t
*/
static std::int32_t to_bson_length(const std::size_t size)
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{
JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
}

return static_cast<std::int32_t>(size);
}

/*!
@brief Writes the given @a element_type and @a name to the output adapter
*/
Expand Down Expand Up @@ -1027,7 +1042,7 @@ class binary_writer
{
write_bson_entry_header(name, 0x02);

write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);
write_number<std::int32_t>(to_bson_length(value.size() + 1ul), true);
oa->write_characters(
reinterpret_cast<const CharType*>(value.c_str()),
value.size() + 1);
Expand Down Expand Up @@ -1142,7 +1157,7 @@ class binary_writer
const typename BasicJsonType::array_t& value)
{
write_bson_entry_header(name, 0x04); // array
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true);
write_number<std::int32_t>(to_bson_length(calc_bson_array_size(value)), true);

std::size_t array_index = 0ul;

Expand All @@ -1162,7 +1177,7 @@ class binary_writer
{
write_bson_entry_header(name, 0x05);

write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);
write_number<std::int32_t>(to_bson_length(value.size()), true);
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));

oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
Expand Down Expand Up @@ -1284,7 +1299,7 @@ class binary_writer
*/
void write_bson_object(const typename BasicJsonType::object_t& value)
{
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true);
write_number<std::int32_t>(to_bson_length(calc_bson_object_size(value)), true);

for (const auto& el : value)
{
Expand Down
23 changes: 19 additions & 4 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17764,6 +17764,21 @@ class binary_writer
return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;
}

/*!
@brief Checks that @a size fits into the 32-bit length field used by BSON
@return The size as a signed 32-bit integer
@throw out_of_range.412 if @a size exceeds the range of std::int32_t
*/
static std::int32_t to_bson_length(const std::size_t size)
{
if (JSON_HEDLEY_UNLIKELY(!value_in_range_of<std::int32_t>(size)))
{
JSON_THROW(out_of_range::create(412, concat("BSON length ", std::to_string(size), " exceeds maximum of ", std::to_string((std::numeric_limits<std::int32_t>::max)())), nullptr));
}

return static_cast<std::int32_t>(size);
}

/*!
@brief Writes the given @a element_type and @a name to the output adapter
*/
Expand Down Expand Up @@ -17812,7 +17827,7 @@ class binary_writer
{
write_bson_entry_header(name, 0x02);

write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);
write_number<std::int32_t>(to_bson_length(value.size() + 1ul), true);
oa->write_characters(
reinterpret_cast<const CharType*>(value.c_str()),
value.size() + 1);
Expand Down Expand Up @@ -17927,7 +17942,7 @@ class binary_writer
const typename BasicJsonType::array_t& value)
{
write_bson_entry_header(name, 0x04); // array
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true);
write_number<std::int32_t>(to_bson_length(calc_bson_array_size(value)), true);

std::size_t array_index = 0ul;

Expand All @@ -17947,7 +17962,7 @@ class binary_writer
{
write_bson_entry_header(name, 0x05);

write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);
write_number<std::int32_t>(to_bson_length(value.size()), true);
write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));

oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());
Expand Down Expand Up @@ -18069,7 +18084,7 @@ class binary_writer
*/
void write_bson_object(const typename BasicJsonType::object_t& value)
{
write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true);
write_number<std::int32_t>(to_bson_length(calc_bson_object_size(value)), true);

for (const auto& el : value)
{
Expand Down
31 changes: 31 additions & 0 deletions tests/src/unit-bson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,35 @@
#include <nlohmann/json.hpp>
using nlohmann::json;

#include <cstdint>
#include <fstream>
#include <limits>
#include <sstream>
#include <vector>
#include "make_test_data_available.hpp"
#include "test_utils.hpp"

namespace
{
// a binary container that reports a size beyond INT32_MAX without allocating
// that much memory, so the BSON length overflow can be tested cheaply
class huge_binary_t : public std::vector<std::uint8_t>
{
public:
using std::vector<std::uint8_t>::vector;

size_type size() const noexcept // NOLINT(readability-convert-member-functions-to-static)
{
// one byte more than the BSON length field can represent
return static_cast<size_type>((std::numeric_limits<std::int32_t>::max)()) + 1;
}
};

using huge_binary_json = nlohmann::basic_json <
std::map, std::vector, std::string, bool, std::int64_t, std::uint64_t,
double, std::allocator, nlohmann::adl_serializer, huge_binary_t, void >;
} // namespace

TEST_CASE("BSON")
{
SECTION("individual values not supported")
Expand Down Expand Up @@ -80,6 +103,14 @@ TEST_CASE("BSON")
#endif
}

SECTION("lengths exceeding INT32_MAX cannot be serialized to BSON")
{
huge_binary_json j;
j["b"] = huge_binary_json::binary(huge_binary_t{});

CHECK_THROWS_WITH_AS(huge_binary_json::to_bson(j), "[json.exception.out_of_range.412] BSON length 2147483661 exceeds maximum of 2147483647", huge_binary_json::out_of_range&);
}

SECTION("string length must be at least 1")
{
// from https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11175
Expand Down
Loading