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
15 changes: 15 additions & 0 deletions docs/mkdocs/docs/home/exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,21 @@ A JSON Patch `move` operation's `"from"` location is a proper prefix of its `"pa

This exception was added in version 3.13.0. Before that, this situation could succeed with a corrupted result: for an array target, removing the "from" element before the "add" step shifted subsequent indices, so "path" silently re-resolved to a different element than intended.

### json.exception.out_of_range.415

MessagePack's ext type and BSON's binary subtype are each stored in a single byte. This exception is thrown when serializing a
[`byte_container_with_subtype`](../api/byte_container_with_subtype/index.md) whose subtype exceeds 255.

!!! failure "Example message"

```
[json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255)
```

!!! note

This exception was added in version 3.13.0. Before that, subtypes above 255 were silently truncated modulo 256 instead of raising an error.

## Further exceptions

This exception is thrown in case of errors that cannot be classified with the
Expand Down
11 changes: 11 additions & 0 deletions include/nlohmann/detail/output/binary_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,11 @@ class binary_writer
// step 1.5: if this is an ext type, write the subtype
if (use_ext)
{
if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j));
}

write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));
}

Expand Down Expand Up @@ -1197,6 +1202,12 @@ class binary_writer
write_bson_entry_header(name, 0x05);

write_number<std::int32_t>(to_bson_length(value.size()), true);

if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), nullptr));
}

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
11 changes: 11 additions & 0 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19302,6 +19302,11 @@ class binary_writer
// step 1.5: if this is an ext type, write the subtype
if (use_ext)
{
if (JSON_HEDLEY_UNLIKELY(j.m_data.m_value.binary->subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(j.m_data.m_value.binary->subtype()), " is too large for the MessagePack ext type (max 255)"), &j));
}

write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));
}

Expand Down Expand Up @@ -19811,6 +19816,12 @@ class binary_writer
write_bson_entry_header(name, 0x05);

write_number<std::int32_t>(to_bson_length(value.size()), true);

if (value.has_subtype() && JSON_HEDLEY_UNLIKELY(value.subtype() > (std::numeric_limits<std::uint8_t>::max)()))
{
JSON_THROW(out_of_range::create(415, concat("subtype ", std::to_string(value.subtype()), " is too large for the BSON binary subtype (max 255)"), nullptr));
}

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
9 changes: 9 additions & 0 deletions tests/src/unit-bson.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,15 @@ TEST_CASE("BSON")
}
}

TEST_CASE("regression test - BSON binary subtype rejects a value that doesn't fit a single byte")
{
json const doc255 = {{"b", json::binary({1, 2}, 255)}};
CHECK(json::from_bson(json::to_bson(doc255))["b"].get_binary().subtype() == 255);

CHECK_THROWS_AS(json::to_bson(json{{"b", json::binary({1, 2}, 256)}}), json::out_of_range);
CHECK_THROWS_WITH_AS(json::to_bson(json{{"b", json::binary({1, 2}, 300)}}), "[json.exception.out_of_range.415] subtype 300 is too large for the BSON binary subtype (max 255)", json::out_of_range);
}

TEST_CASE("BSON input/output_adapters")
{
const json json_representation =
Expand Down
15 changes: 15 additions & 0 deletions tests/src/unit-msgpack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1597,6 +1597,21 @@ TEST_CASE("MessagePack")
}
}

TEST_CASE("regression test - MessagePack ext type rejects a subtype that doesn't fit a single byte")
{
// subtype 0-255 must still round-trip correctly (regression guard, pre-existing behavior)
CHECK(json::from_msgpack(json::to_msgpack(json::binary({1, 2}, 0))).get_binary().subtype() == 0);
CHECK(json::from_msgpack(json::to_msgpack(json::binary({1, 2}, 200))).get_binary().subtype() == 200);
CHECK(json::from_msgpack(json::to_msgpack(json::binary({1, 2}, 255))).get_binary().subtype() == 255);

// a subtype > 255 must throw instead of silently truncating
CHECK_THROWS_AS(json::to_msgpack(json::binary({1, 2}, 256)), json::out_of_range);
CHECK_THROWS_WITH_AS(json::to_msgpack(json::binary({1, 2}, 70000)), "[json.exception.out_of_range.415] subtype 70000 is too large for the MessagePack ext type (max 255)", json::out_of_range);

// a binary value with no subtype at all must be unaffected
CHECK(json::from_msgpack(json::to_msgpack(json::binary({1, 2}))).get_binary().has_subtype() == false);
}

// use this testcase outside [hide] to run it with Valgrind
TEST_CASE("MessagePack nesting does not consume the call stack")
{
Expand Down
Loading