fix: int64 overflow in linear/log iterator reporting level (near INT64_MAX) - #149
Open
filipecosta90 wants to merge 2 commits into
Open
Conversation
Found by adversarial review during the ClusterFuzzLite effort. For a histogram whose highest_trackable_value is near INT64_MAX, the linear and logarithmic iterators advanced their reporting level with 'next += value_units_per_bucket' (iter_linear_next) and 'next *= (int64_t)log_base' (log_iter_next), both of which overflow int64 -> signed-overflow UB, then a negative left-shift when the wrapped value flows into lowest_equivalent_value. Saturate the reporting level at INT64_MAX when the next step would overflow. Crucially, also pin next_value_reporting_level_lowest_equivalent to INT64_MAX in that case: no bucket value ever equals INT64_MAX, so the 'iter->value >= level' emit test can no longer fire and the iterator drains its remaining buckets and terminates rather than re-reporting the saturated level forever. Verified under ASan+UBSan: linear (value_units_per_bucket=2^62) and log (base 2) over an INT64_MAX-range histogram no longer trap and terminate (2 and 64 steps); normal-range iteration is unchanged. Regression test added. Stacked on #148 (whose move_next fix the iterators depend on). Found-by: adversarial review during the ClusterFuzzLite fuzzing effort Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adversarial review of the reporting-level saturation found two more iterator issues in the same functions: - next_value_greater_than_reporting_level_upper_bound peeked hdr_value_at_index(h, counts_index + 1); at the last bucket that reads one past counts[], and value_from_index shifts into the sign bit -> signed-shift UB for a near-INT64_MAX range. It was masked before this PR (the reporting level overflowed first); saturating that unmasked it. Require counts_index+1 to be in range before peeking. - Degenerate iterator parameters looped forever: value_units_per_bucket <= 0 (level never advances) and log_base <= 1 (cast to 0/1, *= never advances). Pin the level (and its lowest-equivalent) to INT64_MAX in those cases too so the emit test cannot re-fire and the iterator drains and terminates. The degenerate checks are ordered first so the guard arithmetic (INT64_MAX - vpb, INT64_MAX / base) can't itself overflow or divide by zero. Verified under ASan+UBSan: the peek repro (hdr_init(1, 1<<62, 1), record 1<<62, linear vpb=INT64_MAX-1 / log base 2) and the degenerate cases (vpb=0, log_base 1.0/0.5/1.5) all terminate with no trap; normal iteration unchanged. Regression tests extended. Found-by: adversarial review during the ClusterFuzzLite fuzzing effort Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Stacked on #148 (the iterators call
move_next, whose value-range overflow #148 fixes). Base this PR on #148; retarget tomainonce #148 merges.Bug (found by adversarial review during the ClusterFuzzLite effort)
For a histogram with
highest_trackable_valuenearINT64_MAX, the linear and log iterators overflow int64 when advancing the reporting level:iter_linear_next(:1162):next_value_reporting_level += value_units_per_bucketlog_iter_next(:1219):next_value_reporting_level *= (int64_t) log_baseBoth are in-contract (public iterators) and reachable via any full linear/log iteration of such a histogram; the wrapped negative level then triggers a negative left-shift in
lowest_equivalent_value.Fix
Saturate the reporting level at
INT64_MAXwhen the next step would overflow. Termination: naive saturation would re-report the top level forever (the emit branch doesn't advancecounts_index). Since no bucket value ever equalsINT64_MAX, pinningnext_value_reporting_level_lowest_equivalenttoINT64_MAXas well makes theiter->value >= leveltest stop firing, so the iterator drains remaining buckets and terminates.Verification (ASan+UBSan)
units=2^62) and log (base 2) over anINT64_MAXhistogram: no trap, terminate (2 and 64 steps). Normal-range iteration unchanged. Regression test added; full suite green.🤖 Generated with Claude Code