fix: signed-overflow UB in top-bucket value-range math (hdr_max/percentile/iterator) - #148
filipecosta90 wants to merge 4 commits into
Conversation
Found by adversarial review during the ClusterFuzzLite effort. For a histogram whose highest_trackable_value is near INT64_MAX, the top bucket's lowest_equivalent_value + size_of_equivalent_value_range exceeds INT64_MAX, which is signed-overflow UB. This poisoned hdr_next_non_equivalent_value -> highest_equivalent_value (hence hdr_max, hdr_value_at_percentile, hdr_percentiles_print) and the move_next iterator step (hence hdr_stddev and any full iteration -- the all-values iterator visits the top bucket regardless of counts). All in-contract and reachable via the read path on a decoded histogram. Saturate both additions at INT64_MAX instead of overflowing. Verified under ASan+UBSan: hdr_init(1, INT64_MAX, 3) + record(INT64_MAX) then hdr_max / percentile / stddev / full iteration / percentiles_print no longer trap and return representable values. Added a regression test. Found-by: adversarial review during the ClusterFuzzLite fuzzing effort Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…variant) Adversarial review of the initial saturation found that deriving highest_equivalent_value as hdr_next_non_equivalent_value(v) - 1 yielded INT64_MAX-1 for the top bucket, so hdr_max of a histogram that recorded INT64_MAX returned a value BELOW it (violating value <= highest_equivalent), and disagreed with move_next() which saturates the iterator's highest_equivalent_value to INT64_MAX. Give highest_equivalent_value its own overflow-aware computation clamping to INT64_MAX so hdr_max / value_at_percentile and the iterator agree. Strengthen the regression test to pin the saturated values (hdr_max == INT64_MAX, hdr_next_non_equivalent_value == INT64_MAX -- the latter distinguishes the fix without a sanitizer, since the buggy code wraps to INT64_MIN). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cedc46a to
92a219f
Compare
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>
|
🤖 Automated first-pass review — a human maintainer's review is still required before merge. The arithmetic checks out. For every config The one point I'd want a human look at is Two smaller notes on the test. Worth stating plainly for whoever merges this: the per-PR ClusterFuzzLite job runs ASan only, so signed-overflow UB of this kind won't be regression-guarded there (only the weekly batch runs UBSan). The added |
# Conflicts: # test/hdr_histogram_test.c
In-contract int64 overflow in top-bucket value-range math, found by adversarial review during the ClusterFuzzLite effort
For a histogram whose
highest_trackable_valueis nearINT64_MAX, the top bucket'slowest_equivalent_value + size_of_equivalent_value_rangeexceedsINT64_MAX→ signed-overflow UB. Two arithmetic sites:hdr_next_non_equivalent_value(:294) andhighest_equivalent_value(:307) → poisonshdr_max,hdr_value_at_percentile, and the value-range lines ofhdr_percentiles_print.move_next(:918, the all-values iterator, which visits every bucket regardless of counts) → the iterator'shighest_equivalent_value.In-contract (
hdr_initaccepts up toINT64_MAX) and reachable from an untrusted decoded log (decode →hdr_reset_internal_counters→highest_equivalent_value; andlog_reader_fuzzercallshdr_max/hdr_value_at_percentileon every decoded histogram).Fix
Saturate all three value-range computations at
INT64_MAXinstead of overflowing.highest_equivalent_valuegets its own overflow-aware clamp (notnext_non_equivalent - 1), sohdr_maxof a histogram that recordedINT64_MAXcorrectly returnsINT64_MAXand agrees with the iterator path.Scope (precise)
This PR fixes the value-range additions only. The related
hdr_meancount * valueoverflow (which also feedshdr_stddevand thehdr_percentiles_printCLASSIC footer) is a separate overflow class fixed in the companion #147. Both are needed for a fully overflow-free read path on near-INT64_MAXhistograms. The linear/log iterator reporting-level overflows are addressed separately (follow-up PR).Verification
Under ASan+UBSan:
hdr_max/ percentiles / value-range iteration no longer trap and return representable values (INT64_MAX); normal histograms are byte-identical (guard only triggers on genuine top-bucket overflow). Regression test added and pins the saturated values. Full suite green.🤖 Generated with Claude Code