diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index c183a94fb..4f8ab79f2 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -1846,6 +1846,29 @@ class binary_reader return get_ubjson_value(get_char ? get_ignore_noop() : current); } + /*! + @brief reject a negative UBJSON/BJData string length + + String and key lengths are written with signed integer markers (i, I, l, + L). A negative value is malformed; without this check get_string() would + silently treat it as an empty string and leave the following bytes to be + misread as the next value. This mirrors the non-negative check the + optimized-container count path already performs in get_ubjson_size_value. + + @param[in] len the string length read from the input + @return whether the length is valid (non-negative) + */ + template + bool check_ubjson_string_length(const NumberType len) + { + if (JSON_HEDLEY_UNLIKELY(len < 0)) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "string length must not be negative", "string"), nullptr)); + } + return true; + } + /*! @brief reads a UBJSON string @@ -1883,25 +1906,25 @@ class binary_reader case 'i': { std::int8_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'I': { std::int16_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'l': { std::int32_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'L': { std::int64_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'u': diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index e4c19c2c3..580b9bd02 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -12395,6 +12395,29 @@ class binary_reader return get_ubjson_value(get_char ? get_ignore_noop() : current); } + /*! + @brief reject a negative UBJSON/BJData string length + + String and key lengths are written with signed integer markers (i, I, l, + L). A negative value is malformed; without this check get_string() would + silently treat it as an empty string and leave the following bytes to be + misread as the next value. This mirrors the non-negative check the + optimized-container count path already performs in get_ubjson_size_value. + + @param[in] len the string length read from the input + @return whether the length is valid (non-negative) + */ + template + bool check_ubjson_string_length(const NumberType len) + { + if (JSON_HEDLEY_UNLIKELY(len < 0)) + { + return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, + exception_message(input_format, "string length must not be negative", "string"), nullptr)); + } + return true; + } + /*! @brief reads a UBJSON string @@ -12432,25 +12455,25 @@ class binary_reader case 'i': { std::int8_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'I': { std::int16_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'l': { std::int32_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'L': { std::int64_t len{}; - return get_number(input_format, len) && get_string(input_format, len, result); + return get_number(input_format, len) && check_ubjson_string_length(len) && get_string(input_format, len, result); } case 'u': diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index 5af4afac1..a60d0a164 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -2721,6 +2721,19 @@ TEST_CASE("BJData") CHECK_THROWS_WITH_AS(_ = json::from_bjdata(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing BJData string: expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x31", json::parse_error&); } + SECTION("negative length") + { + json _; + + std::vector const vi = {'S', 'i', 0xFF}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing BJData string: string length must not be negative", json::parse_error&); + CHECK(json::from_bjdata(vi, true, false).is_discarded()); + + std::vector const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing BJData string: string length must not be negative", json::parse_error&); + CHECK(json::from_bjdata(vl, true, false).is_discarded()); + } + SECTION("parse bjdata markers in ubjson") { // create a single-character string for all number types diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 8242a0cc8..6df9acfac 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -1862,6 +1862,31 @@ TEST_CASE("UBJSON") json _; CHECK_THROWS_WITH_AS(_ = json::from_ubjson(v), "[json.exception.parse_error.113] parse error at byte 2: syntax error while parsing UBJSON string: expected length type specification (U, i, I, l, L); last byte: 0x31", json::parse_error&); } + + SECTION("negative length") + { + json _; + + std::vector const vi = {'S', 'i', 0xFF}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vi), "[json.exception.parse_error.113] parse error at byte 3: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&); + CHECK(json::from_ubjson(vi, true, false).is_discarded()); + + std::vector const vI = {'S', 'I', 0xFF, 0xFF}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vI), "[json.exception.parse_error.113] parse error at byte 4: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&); + CHECK(json::from_ubjson(vI, true, false).is_discarded()); + + std::vector const vl = {'S', 'l', 0xFF, 0xFF, 0xFF, 0xFF}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vl), "[json.exception.parse_error.113] parse error at byte 6: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&); + CHECK(json::from_ubjson(vl, true, false).is_discarded()); + + std::vector const vL = {'S', 'L', 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vL), "[json.exception.parse_error.113] parse error at byte 10: syntax error while parsing UBJSON string: string length must not be negative", json::parse_error&); + CHECK(json::from_ubjson(vL, true, false).is_discarded()); + + // a length of zero remains valid and yields an empty string + std::vector const v0 = {'S', 'i', 0}; + CHECK(json::from_ubjson(v0) == json("")); + } } SECTION("array")