Skip to content

fix: int64 overflow in linear/log iterator reporting level (near INT64_MAX) - #149

Open
filipecosta90 wants to merge 2 commits into
fix/top-bucket-value-range-overflowfrom
fix/iterator-reporting-level-overflow
Open

fix: int64 overflow in linear/log iterator reporting level (near INT64_MAX)#149
filipecosta90 wants to merge 2 commits into
fix/top-bucket-value-range-overflowfrom
fix/iterator-reporting-level-overflow

Conversation

@filipecosta90

Copy link
Copy Markdown
Contributor

Stacked on #148 (the iterators call move_next, whose value-range overflow #148 fixes). Base this PR on #148; retarget to main once #148 merges.

Bug (found by adversarial review during the ClusterFuzzLite effort)

For a histogram with highest_trackable_value near INT64_MAX, the linear and log iterators overflow int64 when advancing the reporting level:

  • iter_linear_next (:1162): next_value_reporting_level += value_units_per_bucket
  • log_iter_next (:1219): next_value_reporting_level *= (int64_t) log_base

Both 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_MAX when the next step would overflow. Termination: naive saturation would re-report the top level forever (the emit branch doesn't advance counts_index). Since no bucket value ever equals INT64_MAX, pinning next_value_reporting_level_lowest_equivalent to INT64_MAX as well makes the iter->value >= level test stop firing, so the iterator drains remaining buckets and terminates.

Verification (ASan+UBSan)

  • Linear (units=2^62) and log (base 2) over an INT64_MAX histogram: no trap, terminate (2 and 64 steps). Normal-range iteration unchanged. Regression test added; full suite green.

🤖 Generated with Claude Code

fcostaoliveira and others added 2 commits July 23, 2026 19:17
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>
@filipecosta90
filipecosta90 requested review from giltene and mikeb01 July 24, 2026 09:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants