From 72ee7d596b49ae43068d8d0ab72f6f124d4fade4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 20 Jul 2026 10:40:09 +0000 Subject: [PATCH] fix: reject non-finite values from digit overflow path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scientific notation already rejected ±inf/NaN, but integer_overflow accepted ~310+ digit magnitudes as IEEE infinity. Guard digit accumulation and finalize paths (including fraction) so parse fails instead of storing non-JSON numeric values. Co-authored-by: Kaius Ruokonen --- xson/xson-json-decoder.test.c++ | 10 ++++++++++ xson/xson-json.c++m | 24 ++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/xson/xson-json-decoder.test.c++ b/xson/xson-json-decoder.test.c++ index bb0b7fb..2446dee 100644 --- a/xson/xson-json-decoder.test.c++ +++ b/xson/xson-json-decoder.test.c++ @@ -41,6 +41,16 @@ auto register_tests() require_false(ob["huge_num"s].is_integer()); }; + // Digit-only magnitudes beyond ~1e308 overflow double to ±inf in integer_overflow. + // Scientific notation already rejected non-finite results; the plain digit path must too + // (otherwise parse accepts Inf and stringify can emit non-JSON). + test_case("DigitOverflowToInfinityRejected, [xson]") = [] { + const auto huge = "1"s + std::string(400, '0'); + require_throws([&]{ (void)json::parse(huge); }); + require_throws([&]{ (void)json::parse("-"s + huge); }); + require_throws([&]{ (void)json::parse(huge + ".5"); }); + }; + test_case("SmallIntegerRemainsInt, [xson]") = [] { // Test that small integers remain as integers auto json_str = R"({"small": 42, "zero": 0, "negative": -123})"; diff --git a/xson/xson-json.c++m b/xson/xson-json.c++m index 6d28ae6..c738742 100644 --- a/xson/xson-json.c++m +++ b/xson/xson-json.c++m @@ -663,6 +663,11 @@ private: if(std::isdigit(c)) { m_number = m_number * 10.0 + static_cast(c - '0'); + // ~310+ digits overflow double to ±inf; reject like the scientific-notation path. + if(!std::isfinite(m_number)) + { + throw std::runtime_error{"JSON parse error: number is not finite (infinity or NaN)"s}; + } } else if(c == '.') { @@ -689,6 +694,10 @@ private: } const xson::number_type final_value = Sign * m_number; + if(!std::isfinite(final_value)) + { + throw std::runtime_error{"JSON parse error: number is not finite (infinity or NaN)"s}; + } m_builder.value(final_value); m_number = 0; m_integer = 0; @@ -722,7 +731,13 @@ private: { if(c == '\0') { - m_builder.value(Sign * m_number); + const xson::number_type final_value = Sign * m_number; + // Integer-overflow → fraction can carry ±inf into this state. + if(!std::isfinite(final_value)) + { + throw std::runtime_error{"JSON parse error: number is not finite (infinity or NaN)"s}; + } + m_builder.value(final_value); m_number = 0; m_place = 1; m_state_machine.pop(); @@ -751,7 +766,12 @@ private: throw std::runtime_error{"JSON parse error: unexpected character in number: '"s + c + "'"s}; } - m_builder.value(Sign * m_number); + const xson::number_type final_value = Sign * m_number; + if(!std::isfinite(final_value)) + { + throw std::runtime_error{"JSON parse error: number is not finite (infinity or NaN)"s}; + } + m_builder.value(final_value); m_number = 0; m_place = 1; m_state_machine.pop();