ks_json_check: decimal_between_zero_and_one mixed an exact floor with a truncating ceiling - #263
Open
mjerris wants to merge 1 commit into
Open
ks_json_check: decimal_between_zero_and_one mixed an exact floor with a truncating ceiling#263mjerris wants to merge 1 commit into
mjerris wants to merge 1 commit into
Conversation
… a truncating ceiling `ks_json_check_number_is_decimal_between_zero_and_one` read `valuedouble` for its lower bound but `valueint` for its upper. `valueint` is cJSON's saturating truncation toward zero, so `valueint <= 1` admits every value below 2, and the predicate enforced (0,2) rather than the [0,1] its name promises. The defect ran in two directions at once: 0.0 REJECTED -- though the string_ twin one function above accepts it 1.5 ACCEPTED -- the whole [1,2) band the name excludes 1.9999 ACCEPTED 2.0 rejected -- so the over-accept band is exactly [1,2) So a caller's accepted set depended on whether the value arrived number- or string-encoded, for two functions with the same name and the same documented contract. `ks_json_check_string_is_decimal_between_zero_and_one` (:82) was already correct, using atof on both bounds with an inclusive lower. This change makes the number_ arm agree with it exactly: valuedouble on both bounds, inclusive lower. The two variants now enforce the same domain. The inclusive lower bound is taken from the string_ twin rather than from the name alone: both variants landed together, and the author's contemporaneous reading of "between zero and one" includes zero. No callers exist in this repo (2 definitions, 2 declarations, 0 call sites), so nothing here changes behaviour today. The exposure is a downstream consumer calling a public API whose name reads as obviously safe for a 0..1 parameter and silently getting (0,2) with 0.0 refused. The identical defect was fixed in mod_infrastructure's parallel copy (cjson_check.c:98), which is where it was found. The two copies were written independently and drifted the same way; this restores them to agreement.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ks_json_check_number_is_decimal_between_zero_and_onereadsvaluedoublefor itslower bound but
valueintfor its upper.valueintis cJSON's saturating truncationtoward zero, so
valueint <= 1admits every value below 2 — the predicate enforces(0, 2), not the[0, 1]its name promises.The defect runs in two directions at once
-0.50.0string_twin has always accepted it0.51.01.5[1,2)band the name excludes1.99992.0[1,2)42Three values change; each is a correction. Everything else is untouched, so this
narrows to the documented domain rather than relaxing anything.
The two same-named variants disagreed with each other
ks_json_check_string_is_decimal_between_zero_and_one(src/ks_json_check.c:82) wasalready correct —
atofon both bounds, inclusive lower:So a caller's accepted set depended on whether the value arrived number- or
string-encoded, for two functions sharing a name and a documented contract. This change
makes the
number_arm agree with thestring_arm exactly.The inclusive lower bound is taken from the twin rather than from the name alone:
both variants landed in the same commit, and the author's contemporaneous reading of
"between zero and one" includes zero.
Impact
No callers exist in this repo — 2 definitions, 2 declarations, 0 call sites — so nothing
here changes behaviour today. The exposure is a downstream consumer calling a public API
whose name reads as obviously safe for a
0..1parameter and silently getting(0, 2)with
0.0refused.Provenance
Found while auditing a class of validator-truncation defects in the SignalWire spec
pipeline, where predicates read
valueintand therefore compare a truncated valueagainst a bound spelled for a real number. The identical defect exists in
mod_infrastructure's independently-written parallel copy (
cjson_check.c:98) and hasbeen fixed there in the same way; the two copies had drifted identically. This PR is the
other half — the defect survives in any consumer linking libks until both land.
This one is unusual within that class: the others truncate consistently, so their
enforced bound is merely shifted. This one is inconsistent within a single
&&, andinconsistent with its own correct twin.