Fix signed integer overflow in toStartOfInterval for Millisecond/Microsecond intervals by alexey-milovidov · Pull Request #100156 · ClickHouse/ClickHouse · GitHub
Skip to content

Fix signed integer overflow in toStartOfInterval for Millisecond/Microsecond intervals#100156

Merged
alexey-milovidov merged 20 commits into
masterfrom
fix-ubsan-toStartOfInterval-overflow
Apr 9, 2026
Merged

Fix signed integer overflow in toStartOfInterval for Millisecond/Microsecond intervals#100156
alexey-milovidov merged 20 commits into
masterfrom
fix-ubsan-toStartOfInterval-overflow

Conversation

@alexey-milovidov

@alexey-milovidov alexey-milovidov commented Mar 20, 2026

Copy link
Copy Markdown
Member

The multiplication interval * scale_diff in ToStartOfInterval<Millisecond> and ToStartOfInterval<Microsecond> could overflow Int64 when the interval value is very large and the DateTime64 scale requires rescaling. Use mulOverflow to detect this and throw DECIMAL_OVERFLOW instead of triggering undefined behavior.

Found by AST fuzzer (UBSan): https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=100138&sha=f2962399a5dcac395a5540de8d06c3500e263ada&name_0=PR&name_1=AST%20fuzzer%20%28amd_ubsan%29
#100138

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

Fix signed integer overflow (undefined behavior) in toStartOfInterval when called with very large millisecond/microsecond interval values on high-precision DateTime64.

Documentation entry for user-facing changes

  • Documentation is written (mandatory for new features)

Version info

  • Merged into: 26.4.1.737

…crosecond intervals

The multiplication `milliseconds * scale_diff` (and similarly for microseconds)
in `ToStartOfInterval` could overflow Int64 when the interval value is very large
and the DateTime64 scale requires rescaling. Use `mulOverflow` to detect this
and throw `DECIMAL_OVERFLOW` instead of triggering undefined behavior.

Found by AST fuzzer (UBSan):
https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=100138&sha=f2962399a5dcac395a5540de8d06c3500e263ada&name_0=PR&name_1=AST%20fuzzer%20%28amd_ubsan%29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@clickhouse-gh

clickhouse-gh Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

@clickhouse-gh clickhouse-gh Bot added the pr-bugfix Pull request with bugfix, not backported by default label Mar 20, 2026
alexey-milovidov and others added 4 commits March 21, 2026 10:47
…visors

The addition `res += static_cast<OriginResultType>(b)` in `PositiveModuloImpl`
can overflow signed arithmetic when B is unsigned and b > INT64_MAX, because
the cast to signed produces a negative value. Perform the addition in unsigned
arithmetic instead, which is well-defined and produces the correct result since
the mathematical value is always in [0, |b|).

Caught by `FunctionsStress.stress` unit test under UBSan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread src/Functions/DivisionUtils.h Outdated
alexey-milovidov and others added 3 commits March 23, 2026 13:20
Add a stateless test that directly exercises the `positiveModulo` overflow
edge cases with large unsigned divisors, as requested in the code review.
The test covers the specific scenario where the intermediate addition
`res + b` in `PositiveModuloImpl` would overflow signed Int64 arithmetic.

#100156 (comment)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alexey-milovidov

Copy link
Copy Markdown
Member Author

The AST fuzzer (arm_asan_ubsan) failure is unrelated to this PR — it's a pre-existing UBSan issue in BSINumericIndexedVector (issue #100052), already being fixed in #100086.

Comment thread src/Functions/DateTimeTransforms.h Outdated
throw DB::Exception(ErrorCodes::DECIMAL_OVERFLOW, "Numeric overflow");
if (t >= 0) [[likely]] /// When we divide the `t` value we should round the result
return (t + scale_diff / 2) / (microseconds * scale_diff) * microseconds;
return (t + scale_diff / 2) / microseconds_scaled * microseconds;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mulOverflow covers microseconds * scale_diff, but the subsequent rounding expression still has signed-overflow UB for large positive DateTime64 values:

(t + scale_diff / 2) / microseconds_scaled * microseconds

Concrete trace for scale_multiplier = 10^9 (so scale_diff = 1000 in the microsecond branch) and
t = toDateTime64('2262-04-11 23:47:16.854775807', 9) (t == 9223372036854775807):

  • scale_diff / 2 == 500
  • t + 500 overflows Int64 before the division

So this PR still leaves UB reachable in the same code path it is fixing. Please guard the addition with common::addOverflow (or equivalent) and throw DECIMAL_OVERFLOW on overflow.

The same pattern is also present in the millisecond specialization.

Comment thread src/Functions/DateTimeTransforms.h Outdated
throw DB::Exception(ErrorCodes::DECIMAL_OVERFLOW, "Numeric overflow");
if (t >= 0) [[likely]] /// When we divide the `t` value we should round the result
return (t + scale_diff / 2) / (milliseconds * scale_diff) * milliseconds;
return (t + scale_diff / 2) / milliseconds_scaled * milliseconds;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same overflow pattern exists in the millisecond specialization:

(t + scale_diff / 2) / milliseconds_scaled * milliseconds

For large positive t close to INT64_MAX and scale_multiplier > 1000, t + scale_diff / 2 can overflow before division. Please apply the same common::addOverflow guard here and throw DECIMAL_OVERFLOW on overflow.

alexey-milovidov and others added 6 commits March 30, 2026 03:48
…Interval-overflow

# Conflicts:
#	src/Functions/DateTimeTransforms.h
Use already-computed `microseconds_scaled` / `milliseconds_scaled` instead of
recomputing the same product into a `divisor` variable inside the block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a negative `DateTime64` (pre-1970) in the `toStartOfInterval` overflow
test so it exercises the `else` branch where master lacked the `mulOverflow`
check. The previous test used a positive timestamp which hit the `t >= 0`
branch where the overflow check already existed on master, causing bugfix
validation to fail (test passed on both old and new code).

Remove the `positiveModulo` overflow test since the underlying fix was
already merged to master via a separate PR, making this test irrelevant
for bugfix validation of this PR.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@alexey-milovidov

Copy link
Copy Markdown
Member Author

The Stress test (arm_msan) failure is fixed by #101239, which should be merged first. After it is merged, please update the branch to include the fix.

@clickhouse-gh

clickhouse-gh Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 84.00% 84.00% +0.00%
Functions 90.90% 90.90% +0.00%
Branches 76.50% 76.50% +0.00%

Changed lines: 100.00% (30/30) | lost baseline coverage: 32 line(s) · Uncovered code

Full report · Diff report

@alexey-milovidov alexey-milovidov merged commit e6b1dea into master Apr 9, 2026
163 of 164 checks passed
@alexey-milovidov alexey-milovidov deleted the fix-ubsan-toStartOfInterval-overflow branch April 9, 2026 19:27
@robot-ch-test-poll3 robot-ch-test-poll3 added the pr-synced-to-cloud The PR is synced to the cloud repo label Apr 9, 2026
@clickgapai

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-bugfix Pull request with bugfix, not backported by default pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants