diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index 8a6a0a6d5..ed7ced0d4 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3123,6 +3123,27 @@ class binary_reader return true; } + /*! + @brief store the count of an optimized container (or the length of a string) + + 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. + + @param[in] count the count read from the input + @param[out] result set to @a count unless it is rejected + @return whether @a count is usable + */ + bool store_ubjson_count(const std::size_t count, std::size_t& result) + { + if (JSON_HEDLEY_UNLIKELY(count == npos)) + { + return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); + } + result = count; + return true; + } + /*! @brief read a UBJSON/BJData optimized-container count of a signed marker type ('i', 'I', 'l', 'L') and narrow it to std::size_t @@ -3153,8 +3174,7 @@ class binary_reader { return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); } - result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char - return true; + return store_ubjson_count(static_cast(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char } /*! @@ -3231,8 +3251,7 @@ class binary_reader { return false; } - result = conditional_static_cast(number); - return true; + return store_ubjson_count(conditional_static_cast(number), result); } case 'M': @@ -3250,8 +3269,7 @@ class binary_reader { return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); } - result = detail::conditional_static_cast(number); - return true; + return store_ubjson_count(detail::conditional_static_cast(number), result); } case '[': diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 693320ed5..504181eb0 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -21130,6 +21130,27 @@ class binary_reader return true; } + /*! + @brief store the count of an optimized container (or the length of a string) + + 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. + + @param[in] count the count read from the input + @param[out] result set to @a count unless it is rejected + @return whether @a count is usable + */ + bool store_ubjson_count(const std::size_t count, std::size_t& result) + { + if (JSON_HEDLEY_UNLIKELY(count == npos)) + { + return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); + } + result = count; + return true; + } + /*! @brief read a UBJSON/BJData optimized-container count of a signed marker type ('i', 'I', 'l', 'L') and narrow it to std::size_t @@ -21160,8 +21181,7 @@ class binary_reader { return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); } - result = static_cast(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char - return true; + return store_ubjson_count(static_cast(number), result); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char } /*! @@ -21238,8 +21258,7 @@ class binary_reader { return false; } - result = conditional_static_cast(number); - return true; + return store_ubjson_count(conditional_static_cast(number), result); } case 'M': @@ -21257,8 +21276,7 @@ class binary_reader { return out_of_range_error(exception_id::container_too_large, "integer value overflow", "size"); } - result = detail::conditional_static_cast(number); - return true; + return store_ubjson_count(detail::conditional_static_cast(number), result); } case '[': diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 04ee3acc4..715c9882f 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3211,14 +3211,34 @@ TEST_CASE("BJData") #endif CHECK(json::from_bjdata(vM, true, false).is_discarded()); -#if SIZE_MAX != 0xffffffff - CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: excessive ndarray size caused overflow", json::out_of_range&); -#else + // the first dimension is 2^64-1, which is rejected as a count (see below) CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vMX), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); -#endif CHECK(json::from_bjdata(vMX, true, false).is_discarded()); } + SECTION("optimized array: count equal to SIZE_MAX") + { + // the reader marks a container without a count by SIZE_MAX, so + // a count of that value must not be read as "no count" + // (it used to parse as [1]) + json _; +#if SIZE_MAX != 0xffffffff + std::vector const vM = {'[', '#', 'M', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vM, true, false).is_discarded()); + // one less is an ordinary excessive count + std::vector const vM1 = {'[', '#', 'M', 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vM1), "[json.exception.out_of_range.408] excessive array size: 18446744073709551614", json::out_of_range&); +#else + std::vector const vm = {'[', '#', 'm', 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vm), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vm, true, false).is_discarded()); + std::vector const vL = {'[', '#', 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 'i', 0x01, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vL), "[json.exception.out_of_range.408] syntax error while parsing BJData size: integer value overflow", json::out_of_range&); + CHECK(json::from_bjdata(vL, true, false).is_discarded()); +#endif + } + SECTION("optimized array: integer value overflow") { #if SIZE_MAX == 0xffffffff diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 04983da1c..6e985c88e 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2360,6 +2360,18 @@ TEST_CASE("UBJSON optimized arrays of a valueless type are bounded") } } + SECTION("a count equal to SIZE_MAX is rejected") + { + // the reader marks a container without a count by SIZE_MAX, so a + // count of that value must not be read as "no count"; UBJSON's + // largest count type (int64) can only reach it where size_t is 32 bits +#if SIZE_MAX == 0xffffffff + const std::vector input = {'[', '#', 'L', 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 'i', 0x01, ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(input), "[json.exception.out_of_range.408] syntax error while parsing UBJSON size: integer value overflow", json::out_of_range&); + CHECK(json::from_ubjson(input, true, false).is_discarded()); +#endif + } + SECTION("ordinary counts are unaffected") { CHECK(json::from_ubjson(std::vector({'[', '$', 'Z', '#', 'i', 3})) == json({nullptr, nullptr, nullptr}));