reject out-of-range float to integer conversion in from_json - #5370
reject out-of-range float to integer conversion in from_json#5370Angadi56 wants to merge 2 commits into
Conversation
Casting a floating-point value outside the target integer's range to that type is undefined behavior. Range-check the value before the cast in from_json and throw out_of_range.406 when it does not fit, applied at both conversion sites. Signed-off-by: Angadi Yashaswini <angadi@digiscrypt.com>
|
https://json.nlohmann.me/api/basic_json/get/#complexity Undefined behavior for numeric conversions Conversions between numeric types are performed by the corresponding from_json() implementation using the target C++ type. When converting between numeric types, the library does not check whether the source value is representable by the target type. If the source value is outside the range of the target type, the behavior is the same as the corresponding C++ conversion. In particular, converting a floating-point value to an integer type that cannot represent the value results in undefined behavior. See Number conversion for more information. https://json.nlohmann.me/features/types/number_handling/#number-conversion |
The get() API reference and the number conversion page still described the out-of-range float to integer cast as unchecked undefined behavior. Describe the new out_of_range.406 behavior there and keep the unchecked wording for integer to integer conversions, which are unchanged. Signed-off-by: Angadi Yashaswini <angadi@digiscrypt.com>
|
Fair point, this is documented behavior, so this PR is really proposing to change that contract for the float to integer case rather than fixing an undocumented bug. The reason I think it's worth changing: the cast is reachable from json::parse with ordinary input (1e40, or an integer literal that overflows uint64, both get stored as number_float), so the undefined behavior triggers on untrusted data rather than on misuse of the API, and the parser already throws out_of_range.406 for numbers it can't store, so raising the same error here keeps things consistent. Integer to integer conversions are untouched and still behave as documented. I updated the two pages you linked so they describe the new behavior. |
When a JSON number is read as an integer through get() or one of its siblings, from_json converts the stored value with a plain static_cast. For a value parsed as a floating-point number that means casting a double straight to the target integer type, and when the double falls outside that type's range the cast is undefined behavior under [conv.fpint]. The value is attacker-reachable: json::parse stores any fractional or exponent input as a number_float, and an integer literal that overflows uint64 such as 99999999999999999999999999 is kept as a double too, so ordinary-looking input reaches the cast. I ran into it building a corpus for get<int32_t>() where UBSan flagged the conversion of 1e40. The fix routes the number_float case through a small helper that range-checks the value against the destination integer before converting and throws out_of_range.406 when it does not fit, which mirrors the overflow error the parser already raises for numbers it cannot store. In-range values still truncate toward zero exactly as before, and floating-point destinations keep the plain cast. I applied it at both conversion sites and added a regression test.
make amalgamate.