From dca9d49a335b6b13b6de70559dbcd3dc148add54 Mon Sep 17 00:00:00 2001 From: Angadi56 Date: Wed, 5 Aug 2026 17:13:36 +0530 Subject: [PATCH] reject out-of-range code points in UTF-32 wide-string input (#5348) * reject out-of-range code points in UTF-32 wide-string input Signed-off-by: Angadi Yashaswini * remove useless cast to char_traits::int_type Signed-off-by: Angadi Yashaswini --------- Signed-off-by: Angadi Yashaswini --- include/nlohmann/detail/input/input_adapters.hpp | 8 ++++++-- single_include/nlohmann/json.hpp | 8 ++++++-- tests/src/unit-wstring.cpp | 10 ++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 2c3561cbc..ba8df07a6 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -345,8 +345,12 @@ struct wide_string_input_helper } else { - // unknown character - utf8_bytes[0] = static_cast::int_type>(wc); + // A code point above U+10FFFF has no UTF-8 encoding. Passing the + // unit through would narrow it to int, where 0xFFFFFFFF becomes + // char_traits::eof() and would end the input silently, so + // emit a byte that is never valid UTF-8 and let the decoder + // reject it. + utf8_bytes[0] = 0xFF; utf8_bytes_filled = 1; } } diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 958ac5fc9..a42a22d23 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -7348,8 +7348,12 @@ struct wide_string_input_helper } else { - // unknown character - utf8_bytes[0] = static_cast::int_type>(wc); + // A code point above U+10FFFF has no UTF-8 encoding. Passing the + // unit through would narrow it to int, where 0xFFFFFFFF becomes + // char_traits::eof() and would end the input silently, so + // emit a byte that is never valid UTF-8 and let the decoder + // reject it. + utf8_bytes[0] = 0xFF; utf8_bytes_filled = 1; } } diff --git a/tests/src/unit-wstring.cpp b/tests/src/unit-wstring.cpp index ffbe70e7e..a38df3aaa 100644 --- a/tests/src/unit-wstring.cpp +++ b/tests/src/unit-wstring.cpp @@ -125,6 +125,16 @@ TEST_CASE("wide strings") std::u32string const w = U"\"\x110000"; json _; CHECK_THROWS_AS(_ = json::parse(w), json::parse_error&); + + // a code unit above U+10FFFF must not be narrowed onto the EOF + // sentinel: 0xFFFFFFFF would otherwise end the document silently and + // let everything following it pass the strict end-of-input check + std::u32string const trailing{U'[', U'1', U']', static_cast(0xFFFFFFFF), U'x'}; + CHECK_THROWS_WITH_AS(_ = json::parse(trailing), "[json.exception.parse_error.101] parse error at line 1, column 4: syntax error while parsing value - invalid literal; last read: '1]\xFF'; expected end of input", json::parse_error&); + CHECK(!json::accept(trailing)); + + // the same unit inside a string is reported as an ill-formed byte + CHECK_THROWS_WITH_AS(_ = json::parse(std::u32string{U'"', static_cast(0xFFFFFFFF), U'"'}), "[json.exception.parse_error.101] parse error at line 1, column 2: syntax error while parsing value - invalid string: ill-formed UTF-8 byte; last read: '\"\xFF'", json::parse_error&); } } }