From 69874e4544f2a129069ebef8d47eee40ef8c1483 Mon Sep 17 00:00:00 2001 From: fhgffy <102001626+fhgffy@users.noreply.github.com> Date: Wed, 7 Oct 2026 04:30:52 +0800 Subject: [PATCH] Fix NUL bytes in UBJSON/BJData high-precision numbers (#5760) * Fix NUL-terminated UBJSON and BJData high-precision payloads Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> * Use English comments for the high-precision NUL fix Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> * Track NUL bytes while reading high-precision payloads Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> * Drop dates from code comments. Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> * Report a NUL in a high-precision number where it is read Return the parse error from the read loop so the byte offset points at the NUL, and drop the separate check after lexing. Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> * Use fixed text for the high-precision NUL error 2026-10-06: Use the known zero byte directly instead of formatting and concatenating it. Preserve the SAX token, error message, and byte offset. Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> --------- Signed-off-by: fhgffy <102001626+fhgffy@users.noreply.github.com> --- .../nlohmann/detail/input/binary_reader.hpp | 6 +++++ single_include/nlohmann/json.hpp | 6 +++++ tests/src/unit-bjdata.cpp | 26 +++++++++++++++++++ tests/src/unit-ubjson.cpp | 26 +++++++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/include/nlohmann/detail/input/binary_reader.hpp b/include/nlohmann/detail/input/binary_reader.hpp index e6c698fa0..d26e5fad4 100644 --- a/include/nlohmann/detail/input/binary_reader.hpp +++ b/include/nlohmann/detail/input/binary_reader.hpp @@ -3249,6 +3249,12 @@ class binary_reader { return false; } + // the lexer would stop at a NUL and accept the digits before it + if (JSON_HEDLEY_UNLIKELY(current == '\0')) + { + return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, + exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); + } number_vector.push_back(static_cast(current)); } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 9a1aaeecd..09e3b4f4a 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -16998,6 +16998,12 @@ class binary_reader { return false; } + // the lexer would stop at a NUL and accept the digits before it + if (JSON_HEDLEY_UNLIKELY(current == '\0')) + { + return sax->parse_error(chars_read, "00", parse_error::create(115, chars_read, + exception_message("invalid number text; last byte: 0x00", "high-precision number"), nullptr)); + } number_vector.push_back(static_cast(current)); } diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index cd989ef88..af1883827 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -1286,6 +1286,32 @@ TEST_CASE("BJData") CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1A", json::parse_error); std::vector const vec3 = {'H', 'i', 2, '1', '.'}; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text: 1.", json::parse_error); + // Reject NULs where they are read, including trailing NULs and payloads cut off after one. + SECTION("NUL in high-precision number (issue #5753)") + { + for (const auto& vec : std::vector> + { + {'H', 'i', 3, '1', 0, 'x'}, + {'H', 'i', 2, '1', 0}, + {'H', 'i', 3, '1', 0} + }) + { + CAPTURE(vec) + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing BJData high-precision number: invalid number text; last byte: 0x00", json::parse_error); + CHECK(json::from_bjdata(vec, true, false).is_discarded()); + CHECK(json::from_bjdata(vec, false, false).is_discarded()); + } + + std::vector const nested = {'[', 'H', 'i', 3, '1', 0, 'x', ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_bjdata(nested), "[json.exception.parse_error.115] parse error at byte 6: syntax error while parsing BJData high-precision number: invalid number text; last byte: 0x00", json::parse_error); + CHECK(json::from_bjdata(nested, true, false).is_discarded()); + + std::vector const valid = {'H', 'i', 1, '1'}; + const auto j = json::from_bjdata(valid); + CHECK(j.is_number_unsigned()); + CHECK(j == json(1)); + } + std::vector const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'}; CHECK_THROWS_WITH_AS(_ = json::from_bjdata(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range); std::vector const vec4 = {'H', 2, '1', '0'}; diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index 9a45546b6..62abd50fc 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -802,6 +802,32 @@ TEST_CASE("UBJSON") CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec2), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A", json::parse_error); std::vector const vec3 = {'H', 'i', 2, '1', '.'}; CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec3), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1.", json::parse_error); + // Reject NULs where they are read, including trailing NULs and payloads cut off after one. + SECTION("NUL in high-precision number (issue #5753)") + { + for (const auto& vec : std::vector> + { + {'H', 'i', 3, '1', 0, 'x'}, + {'H', 'i', 2, '1', 0}, + {'H', 'i', 3, '1', 0} + }) + { + CAPTURE(vec) + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec), "[json.exception.parse_error.115] parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text; last byte: 0x00", json::parse_error); + CHECK(json::from_ubjson(vec, true, false).is_discarded()); + CHECK(json::from_ubjson(vec, false, false).is_discarded()); + } + + std::vector const nested = {'[', 'H', 'i', 3, '1', 0, 'x', ']'}; + CHECK_THROWS_WITH_AS(_ = json::from_ubjson(nested), "[json.exception.parse_error.115] parse error at byte 6: syntax error while parsing UBJSON high-precision number: invalid number text; last byte: 0x00", json::parse_error); + CHECK(json::from_ubjson(nested, true, false).is_discarded()); + + std::vector const valid = {'H', 'i', 1, '1'}; + const auto j = json::from_ubjson(valid); + CHECK(j.is_number_unsigned()); + CHECK(j == json(1)); + } + std::vector const vec_overflow = {'H', 'i', 5, '1', 'e', '4', '0', '0'}; CHECK_THROWS_WITH_AS(_ = json::from_ubjson(vec_overflow), "[json.exception.out_of_range.406] number overflow parsing '1e400'", json::out_of_range&); std::vector const vec4 = {'H', 2, '1', '0'};