From da6d21ae24ad72df25286544e522722a014c85a1 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Mon, 2 Nov 2026 18:25:06 +0100 Subject: [PATCH] MessagePack reader: reject an array/map 32 length equal to SIZE_MAX On a platform with a 32-bit std::size_t, an array 32 or map 32 length of 0xFFFFFFFF equals unknown_size(), so start_array()/start_object() were told the size was unknown and skipped the max_size() check. Route both through the CBOR length check, renamed get_container_size(), which already rejects that value. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/binary_reader.hpp | 16 ++++++++------- single_include/nlohmann/json.hpp | 16 ++++++++------- tests/src/unit-32bit.cpp | 20 +++++++++++++++++++ tests/src/unit-regression2.cpp | 2 +- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index ed7ced0d4..452dce145 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1024,7 +1024,7 @@ class binary_reader { std::uint64_t len{}; std::size_t size{}; - return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size); + return get_cbor_argument(len) && get_container_size(len, size, "array") && enter_cbor_array(size); } case 0x9F: // array (indefinite length) @@ -1064,7 +1064,7 @@ class binary_reader { std::uint64_t len{}; std::size_t size{}; - return get_cbor_argument(len) && get_cbor_container_size(len, size, "map") && enter_object(size); + return get_cbor_argument(len) && get_container_size(len, size, "map") && enter_object(size); } case 0xBF: // map (indefinite length) @@ -1700,7 +1700,7 @@ class binary_reader } /*! - @brief narrow a definite CBOR array/map length to std::size_t + @brief narrow a definite CBOR or MessagePack array/map length to std::size_t A definite length is rejected if it does not fit in std::size_t or if it equals detail::unknown_size(), which is reserved to mark an indefinite- @@ -1713,7 +1713,7 @@ class binary_reader @param[in] context "array" or "map", for the error message @return whether the length is usable */ - bool get_cbor_container_size(const std::uint64_t len, std::size_t& result, const char* context) + bool get_container_size(const std::uint64_t len, std::size_t& result, const char* context) { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(len) || len == detail::unknown_size())) { @@ -2305,7 +2305,8 @@ class binary_reader case 0xDD: // array 32 { std::uint32_t len{}; - return get_number(len) && enter_msgpack_array(conditional_static_cast(len)); + std::size_t size{}; + return get_number(len) && get_container_size(len, size, "array") && enter_msgpack_array(size); } case 0xDE: // map 16 @@ -2317,7 +2318,8 @@ class binary_reader case 0xDF: // map 32 { std::uint32_t len{}; - return get_number(len) && enter_object(conditional_static_cast(len)); + std::size_t size{}; + return get_number(len) && get_container_size(len, size, "map") && enter_object(size); } // negative fixint @@ -3128,7 +3130,7 @@ class binary_reader A count equal to npos is rejected: npos marks a container whose count is not known, so a count of that value would be read as "no count" (compare - get_cbor_container_size()). No container can hold that many elements. + get_container_size()). No container can hold that many elements. @param[in] count the count read from the input @param[out] result set to @a count unless it is rejected diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 504181eb0..7e766cb8b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -19031,7 +19031,7 @@ class binary_reader { std::uint64_t len{}; std::size_t size{}; - return get_cbor_argument(len) && get_cbor_container_size(len, size, "array") && enter_cbor_array(size); + return get_cbor_argument(len) && get_container_size(len, size, "array") && enter_cbor_array(size); } case 0x9F: // array (indefinite length) @@ -19071,7 +19071,7 @@ class binary_reader { std::uint64_t len{}; std::size_t size{}; - return get_cbor_argument(len) && get_cbor_container_size(len, size, "map") && enter_object(size); + return get_cbor_argument(len) && get_container_size(len, size, "map") && enter_object(size); } case 0xBF: // map (indefinite length) @@ -19707,7 +19707,7 @@ class binary_reader } /*! - @brief narrow a definite CBOR array/map length to std::size_t + @brief narrow a definite CBOR or MessagePack array/map length to std::size_t A definite length is rejected if it does not fit in std::size_t or if it equals detail::unknown_size(), which is reserved to mark an indefinite- @@ -19720,7 +19720,7 @@ class binary_reader @param[in] context "array" or "map", for the error message @return whether the length is usable */ - bool get_cbor_container_size(const std::uint64_t len, std::size_t& result, const char* context) + bool get_container_size(const std::uint64_t len, std::size_t& result, const char* context) { if (JSON_HEDLEY_UNLIKELY(!value_in_range_of(len) || len == detail::unknown_size())) { @@ -20312,7 +20312,8 @@ class binary_reader case 0xDD: // array 32 { std::uint32_t len{}; - return get_number(len) && enter_msgpack_array(conditional_static_cast(len)); + std::size_t size{}; + return get_number(len) && get_container_size(len, size, "array") && enter_msgpack_array(size); } case 0xDE: // map 16 @@ -20324,7 +20325,8 @@ class binary_reader case 0xDF: // map 32 { std::uint32_t len{}; - return get_number(len) && enter_object(conditional_static_cast(len)); + std::size_t size{}; + return get_number(len) && get_container_size(len, size, "map") && enter_object(size); } // negative fixint @@ -21135,7 +21137,7 @@ class binary_reader A count equal to npos is rejected: npos marks a container whose count is not known, so a count of that value would be read as "no count" (compare - get_cbor_container_size()). No container can hold that many elements. + get_container_size()). No container can hold that many elements. @param[in] count the count read from the input @param[out] result set to @a count unless it is rejected diff --git a/tests/src/unit-32bit.cpp b/tests/src/unit-32bit.cpp index 97549c999..e4e2dea6d 100644 --- a/tests/src/unit-32bit.cpp +++ b/tests/src/unit-32bit.cpp @@ -97,3 +97,23 @@ TEST_CASE("CBOR") } } } + +TEST_CASE("MessagePack") +{ + SECTION("parse errors") + { + SECTION("array/map size equal to the unknown-size sentinel") + { + // on 32-bit platforms an array 32/map 32 length of 0xFFFFFFFF aliases unknown_size() + std::vector const varr = {0xDD, 0xFF, 0xFF, 0xFF, 0xFF}; + std::vector const vmap = {0xDF, 0xFF, 0xFF, 0xFF, 0xFF}; + + json _; + CHECK_THROWS_WITH_AS(_ = json::from_msgpack(varr), "[json.exception.out_of_range.408] syntax error while parsing MessagePack size: excessive array size", json::out_of_range&); + CHECK(json::from_msgpack(varr, true, false).is_discarded()); + + CHECK_THROWS_WITH_AS(_ = json::from_msgpack(vmap), "[json.exception.out_of_range.408] syntax error while parsing MessagePack size: excessive map size", json::out_of_range&); + CHECK(json::from_msgpack(vmap, true, false).is_discarded()); + } + } +} diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index c7566c799..fb1f5a66c 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -1005,7 +1005,7 @@ TEST_CASE("regression test - excessive binary container size honors allow_except // allow_exceptions=true (the default) must still throw exactly as before. // The exact message text is not checked here: on platforms where // std::size_t is 32-bit, the CBOR reader's own length-narrowing check - // (get_cbor_container_size(), unrelated to this fix) intercepts a + // (get_container_size(), unrelated to this fix) intercepts a // declared length of 2^63 before it ever reaches the check this test // targets, with different (but equally valid, and already correct) // wording -- see unit-cbor.cpp for coverage of that message.