Skip to content

reject out-of-range float to integer conversion in from_json - #5370

Open
Angadi56 wants to merge 2 commits into
nlohmann:developfrom
Angadi56:from-json-float-int-range
Open

reject out-of-range float to integer conversion in from_json#5370
Angadi56 wants to merge 2 commits into
nlohmann:developfrom
Angadi56:from-json-float-int-range

Conversation

@Angadi56

@Angadi56 Angadi56 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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.

  • The changes are described in detail, both the what and why.
  • If applicable, an existing issue is referenced.
  • The Code coverage remained at 100%. A test case for every new line of code.
  • If applicable, the documentation is updated.
  • The source code is amalgamated by running make amalgamate.

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>
@gregmarr

gregmarr commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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>
@Angadi56

Copy link
Copy Markdown
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants