mirror of
https://github.com/nlohmann/json.git
synced 2026-09-23 10:44:20 +00:00
Reject MessagePack/BSON binary subtypes that don't fit their wire format (#5469)
* Reject MessagePack/BSON binary subtypes that don't fit their wire format Both formats store byte_container_with_subtype's subtype (a uint64_t) in a single byte. The writers cast to std::int8_t/std::uint8_t without a range check, so subtypes above 255 were silently truncated modulo 256 instead of raising an error. Throw out_of_range.413 instead when the subtype exceeds the representable range of 0-255. Signed-off-by: Niels Lohmann <mail@nlohmann.me> * Move the new binary-subtype regression test out of unit-regression2.cpp unit-regression2.cpp is already at the edge of what the MinGW linker can relocate; adding this test's ~26 lines tips test-regression2_cpp20 (clang, Windows) over into "relocation truncated to fit: IMAGE_REL_AMD64_REL32 against `.rdata'" (see8ce64b9c1/b82717c8afor the same failure mode). Split the test along format lines instead: MessagePack assertions move to unit-msgpack.cpp, BSON assertions to unit-bson.cpp. The CBOR round-trip guard is dropped as redundant -- unit-cbor.cpp's "Tagged values" section already round-trips subtypes up to 8589934590, far past the 70000 checked here. Signed-off-by: Niels Lohmann <mail@nlohmann.me> --------- Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -1213,6 +1218,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());
|
||||
|
||||
@@ -19445,6 +19445,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()));
|
||||
}
|
||||
|
||||
@@ -19970,6 +19975,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());
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -1682,6 +1682,21 @@ TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user