Repository navigation
reject non-integer JSON numbers where an integer is expected - #1240
Conversation
json.get<T>() for an integral T converts a floating point number with a plain static_cast, which is undefined behavior outside the range of T, truncates fractions, and wraps out-of-range integers. Read those values through a new JsonToInteger<T> helper that throws RuntimeError instead: the elements of an integer array in JsonExporter::fromJson, the "json:" form of convertFromString<std::vector<int>>, async_delay in loadSubstitutionRuleFromJSON, and the uid/position/mode fields of the Groot2 hook requests. Behavior change: an integer array with a real or out-of-range element, and a fractional or out-of-range async_delay or hook uid, now throw instead of being silently converted.
|
Thanks @aysha-afrah26, nice find: The one thing blocking a merge is that For the record, the behavior changes this introduces, so they can go in the release notes:
Once the helper is internal I'll re-review. |
Move the helper to BT::details: it has to live in an installed header because the inline Monitor::from_json(Hook) uses it, but it is not meant to become public API. Accept a JSON float with no fractional part (100.0, [1, 2.0]) when it is in range, using the same check as Any::cast<T>(). Only lossy values (2.5, 1e300, NaN, out of range) are rejected, so trees and Groot2 messages that spell integers as whole floats keep loading as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
To move this forward I pushed two commits on top of yours (thanks for enabling maintainer edits): a merge of master, and a follow-up that (1) moves the helper to |
nlohmann's
get<T>()for an integralTconverts a JSON float with a barestatic_cast, so a value outside the range ofTis undefined behavior (UBSan float-cast-overflow), a fraction is truncated and an out-of-range integer wraps. Several readers apply it to external JSON:JsonExporter::fromJsonpicksstd::vector<int>from the first array element only and then casts every element (ImportBlackboardFromJSONwith{"v":[1, 1e300]}), ajson:[1e300]value on astd::vector<int>port in the XML,async_delayinloadSubstitutionRuleFromJSON, and theuid/position/modefields of the Groot2HOOK_INSERT,BREAKPOINT_UNLOCKandHOOK_REMOVErequests. On the Groot2 server auidofnode_uid + 65536ornode_uid + 0.5silently installs the hook on that real node, and1e300is UB on the server thread. UBSan reports1e+300 is outside the range of representable values of type 'int'(json.hpp:4983) for the first three, and on master the hook request with a wrapped uid is accepted. #1186 fixed the scalar branch offromJson; this covers the array branch and every otherget<integral>call on external input in the tree (theBT_JSON_CONVERTERfields belong to user types and are left alone). The new inlineJsonToInteger<T>in json_export.h accepts only JSON integers representable asTand throwsRuntimeErrorotherwise, so a bad value takes the existing error paths; the Groot2 server already turns the exception into an error reply.Behavior change: an integer array with a real or out-of-range element, and a fractional or out-of-range
async_delayor hookuid, now throw instead of being converted. Inline header additions only, no ABI change. The new testsJsonTest.IntegerValuesAreRangeChecked,Substitution.ParserRejectsNonIntegerAsyncDelayandGroot2PublisherIntegration.HookRequestRejectsNonIntegerUidfail on master and pass with the change. Full ctest is 549/549 under ASan+UBSan on macOS, and clang-format and codespell are clean (clang-tidy-21 was not available locally).