From da5800cdccebdd531a09399c89df13ea6e57de21 Mon Sep 17 00:00:00 2001 From: Arnaud Bienner Date: Fri, 19 Apr 2024 17:43:17 +0200 Subject: [PATCH 1/3] Check we don't overflow when casting down integers during parsing --- .../nlohmann/detail/conversions/from_json.hpp | 30 +++++++++++++++---- tests/src/unit-class_parser.cpp | 6 ++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index aa2f0cbf4c..00fc68b0a5 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -43,6 +43,24 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) n = nullptr; } +template +ArithmeticTypeTarget static_cast_check_range(const BasicJsonType& j) +{ + const auto val = *j.template get_ptr(); + const auto min = std::numeric_limits::min(); + const auto max = std::numeric_limits::max(); + if (val < min && val > max) + { + JSON_THROW( + out_of_range::create( + 406, + "value " + std::to_string(val) + " is out of target integer range [" + std::to_string(min) + ", " + + std::to_string(max) + "]", &j)); + } + return static_cast(val); +} + + // overloads for basic_json template parameters template < typename BasicJsonType, typename ArithmeticType, enable_if_t < std::is_arithmetic::value&& @@ -54,17 +72,17 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) { case value_t::number_unsigned: { - val = static_cast(*j.template get_ptr()); + val = static_cast_check_range(j); break; } case value_t::number_integer: { - val = static_cast(*j.template get_ptr()); + val = static_cast_check_range(j); break; } case value_t::number_float: { - val = static_cast(*j.template get_ptr()); + val = static_cast_check_range(j); break; } @@ -343,17 +361,17 @@ inline void from_json(const BasicJsonType& j, ArithmeticType& val) { case value_t::number_unsigned: { - val = static_cast(*j.template get_ptr()); + val = static_cast_check_range(j); break; } case value_t::number_integer: { - val = static_cast(*j.template get_ptr()); + val = static_cast_check_range(j); break; } case value_t::number_float: { - val = static_cast(*j.template get_ptr()); + val = static_cast_check_range(j); break; } case value_t::boolean: diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index e2a8bac0f7..bcb2cd55c0 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -585,6 +585,12 @@ TEST_CASE("parser class") CHECK_THROWS_WITH_AS(parser_helper("1.18973e+4932").empty(), "[json.exception.out_of_range.406] number overflow parsing '1.18973e+4932'", json::out_of_range&); } + SECTION("out-of-range-conversion") + { + // overflows during parsing with casting yield an exception + CHECK_THROWS_WITH_AS(parser_helper("123456").get(), "[json.exception.out_of_range.406] value 123456 is out of target integer range [-32768, 32767]", json::out_of_range&); + } + SECTION("invalid numbers") { // numbers must not begin with "+" From bd9d0b5bf88006a5806b86abc2e48b6bf84ad6d0 Mon Sep 17 00:00:00 2001 From: Arnaud Bienner Date: Sat, 20 Apr 2024 19:34:43 +0200 Subject: [PATCH 2/3] * Fix check * Make the check optional if the target type is greater or equal to source type, or if source is floating point number * Ignore infinity values, as they are already handled --- .../nlohmann/detail/conversions/from_json.hpp | 28 +++++++++++++------ tests/src/unit-class_parser.cpp | 3 ++ 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 00fc68b0a5..1da83edc39 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -46,16 +46,26 @@ inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) template ArithmeticTypeTarget static_cast_check_range(const BasicJsonType& j) { - const auto val = *j.template get_ptr(); - const auto min = std::numeric_limits::min(); - const auto max = std::numeric_limits::max(); - if (val < min && val > max) + const ArithmeticTypeSource val = *j.template get_ptr(); + if constexpr (sizeof(ArithmeticTypeTarget) < sizeof(ArithmeticTypeSource) || std::is_floating_point::value) { - JSON_THROW( - out_of_range::create( - 406, - "value " + std::to_string(val) + " is out of target integer range [" + std::to_string(min) + ", " + - std::to_string(max) + "]", &j)); + const ArithmeticTypeSource min = + std::is_signed::value ? std::numeric_limits::lowest() : 0; + const ArithmeticTypeSource max = std::numeric_limits::max(); + bool valIsInf = false; + if constexpr (std::is_floating_point::value) + { + valIsInf = isinf(val); + } + if ((val < min || val > max) && !valIsInf) + { + JSON_THROW( + out_of_range::create( + 406, + "value " + std::to_string(val) + " is out of target integer range [" + + std::to_string(std::numeric_limits::lowest()) + ", " + + std::to_string(std::numeric_limits::max()) + "]", &j)); + } } return static_cast(val); } diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index bcb2cd55c0..114295f192 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -589,6 +589,9 @@ TEST_CASE("parser class") { // overflows during parsing with casting yield an exception CHECK_THROWS_WITH_AS(parser_helper("123456").get(), "[json.exception.out_of_range.406] value 123456 is out of target integer range [-32768, 32767]", json::out_of_range&); + CHECK_THROWS_WITH_AS(parser_helper("65536").get(), "[json.exception.out_of_range.406] value 65536 is out of target integer range [-32768, 32767]", json::out_of_range&); + CHECK_THROWS_WITH_AS(parser_helper("-32769").get(), "[json.exception.out_of_range.406] value -32769 is out of target integer range [-32768, 32767]", json::out_of_range&); + CHECK_THROWS_WITH_AS(parser_helper("18446744073709559808").get(), "[json.exception.out_of_range.406] value 18446744073709559808.000000 is out of target integer range [0, 18446744073709551615]", json::out_of_range&); } SECTION("invalid numbers") From d2f1d904d9f79eeab157ba967c601a5412dcde2a Mon Sep 17 00:00:00 2001 From: Arnaud Bienner Date: Sat, 20 Apr 2024 20:16:33 +0200 Subject: [PATCH 3/3] Use std::isinf --- include/nlohmann/detail/conversions/from_json.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 1da83edc39..11eac0a29c 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -55,7 +55,7 @@ ArithmeticTypeTarget static_cast_check_range(const BasicJsonType& j) bool valIsInf = false; if constexpr (std::is_floating_point::value) { - valIsInf = isinf(val); + valIsInf = std::isinf(val); } if ((val < min || val > max) && !valIsInf) {