Fix signed integer overflow in DateLUTImpl::toStartOfHourInterval#108848
Conversation
`UndefinedBehaviorSanitizer` reported a signed integer overflow in the master `Stress test (amd_asan_ubsan)` job: the AST fuzzer mutated the regression test added in #108766 into a multi-hour `toIntervalHour` variant and reached the final `date + time` reconstruction in `DateLUTImpl::toStartOfHourInterval`, which overflows below the minimum of `Int64` for `DateTime64` values far outside any valid date range. Unlike `toStartOfMinuteInterval` and `roundDown`, `toStartOfHourInterval` has no fast path, so even UTC goes through this computation. The `INTERVAL 1 HOUR` case in the existing test short-circuits to `toStartOfHour` and never reached the overflowing line, so only intervals of more than one hour exercise it. Add a private static helper `addSaturating` that performs the final addition saturating at the `Time` boundaries instead of overflowing. For valid in-range arguments the result is unchanged; for out-of-range extremes (whose result is meaningless anyway) it saturates rather than invoking undefined behavior. Regression test: `tests/queries/0_stateless/04489_tostartof_hour_interval_extreme_overflow.sql`. CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?REF=master&sha=f4c91fb747098bd4f69e0889ea3ca47f52322b06&name_0=MasterCI&name_1=Stress%20test%20%28amd_asan_ubsan%29 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `Build (arm_tidy)` job failed because `Time res;` in the new `addSaturating` helper is declared uninitialized. `clang-tidy` treats `cppcoreguidelines-init-variables` as an error. Initialize it to `0` (the value is always overwritten by `__builtin_add_overflow`, this just satisfies the check). CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=108848&sha=4ad680f167c2e9b50648f6fe3a53cf8073b6460c&name_0=PR&name_1=Build%20%28arm_tidy%29
…st overflow `toStartOfInterval` only validates that the interval value is positive, and `IntervalHour` is backed by `Int64`, so a huge but positive hour count reaches `toStartOfHourInterval`. There `UInt64 seconds = hours * 3600` wraps; for `toIntervalHour(4611686018427387904)` it wraps to exactly `0`, and the following `time / seconds` divides by zero (undefined behavior / `SIGFPE`) before reaching the saturated reconstruction added earlier in this pull request. Detect the multiplication overflow with `__builtin_mul_overflow` and saturate the divisor to the maximum, consistent with the saturating philosophy of this change: the rounding result for such out-of-range interval counts is meaningless anyway, so a normal value simply rounds down to the start of its day instead of trapping. Extend `04489_tostartof_hour_interval_extreme_overflow` with the overflowing interval-count cases (found by the AI review).
|
Continued work on this PR:
Verification (full binary rebuild from this widely-included header is infeasible here):
|
…gainst overflow This is the `MINUTE` sibling of the `toStartOfHourInterval` overflow guarded in the previous commit. `toStartOfInterval` only validates that the interval value is positive, so a huge but positive `toIntervalMinute` reaches `toStartOfMinuteInterval`, where `Int64 divisor = 60 * minutes` wraps. `INTERVAL 4611686018427387904 MINUTE` wraps the divisor to exactly zero (`60 * 2^62 ≡ 0 mod 2^64`), and the following division by it is undefined behaviour: a division-by-zero trap on x86 and a `UndefinedBibSanitizer` report under sanitizers (on ARM the hardware returns zero, which is why the release binary silently produced `1970-01-01 00:00:00`). The conversion now detects the multiplication overflow with `__builtin_mul_overflow` and also clamps a product that exceeds the maximum of `Int64`, saturating the divisor to `Int64::max`. For in-range interval counts the divisor is unchanged; for meaningless extreme counts a normal value simply rounds down to the start of the epoch instead of trapping. A new regression test `04489_tostartof_minute_interval_extreme_overflow` covers the overflowing interval-count cases for the `MINUTE` path that the `HOUR` test missed.
|
Continued work on this PR:
Verification (full binary rebuild from this widely-included header is infeasible here):
The three remaining red |
`04489_max_threads_auto_parsing_compat` has since merged to `master`, taking the `04489` prefix this PR's two regression tests were using. Renumber them to the next free prefixes against `master` so they do not share a number with an already-merged test (matching what `add-test` would assign today): - `04489_tostartof_hour_interval_extreme_overflow` -> `04490_...` - `04489_tostartof_minute_interval_extreme_overflow` -> `04491_...` The cross-reference comment in the `MINUTE` test is updated to point at the renamed `HOUR` test. The SQL and reference contents are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Continued work on this PR:
State: AI Review verdict is ✅ Approve, all review threads resolved (0 unresolved), and the PR is |
…ster Since the previous CI run, master gained new stateless tests using the `04490` and `04491` prefixes that this PR's two regression tests were occupying (`04490_dict_get_keys_float_signed_zero_consistency`, `04490_table_readonly_partition_commands`, `04491_dict_get_keys_const_path_non_coordinator_layout`, `04491_table_readonly_move_partition_to_table`). Merging this PR as-is would leave three tests sharing each of those prefixes. Renamed to the next free prefixes against `master` (what `add-test` would assign today): 04490_tostartof_hour_interval_extreme_overflow -> 04492_... 04491_tostartof_minute_interval_extreme_overflow -> 04493_... The cross-reference comment in the `MINUTE` test was updated to point at the renamed `HOUR` test. SQL and reference contents are otherwise unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The `MINUTE` regression test's header comment pointed at the old `04490_tostartof_hour_interval_extreme_overflow` name; update it to the renamed `04492_tostartof_hour_interval_extreme_overflow`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Continued work on this PR:
State: AI Review verdict is ✅ Approve, both review threads (the The three red jobs on the previous head were all known master-wide flakes, unrelated to this date-arithmetic change:
CI re-runs from scratch on the renumbered head. |
|
@groeneai, please investigate this unrelated CI failure and provide a fix in a separate PR (or link an in-progress one). It is not caused by this PR (a
Findings so far:
|
|
@alexey-milovidov Confirmed: this is the Fix is already approved and green: #108560 allowlists both the PG ( Tracking issue: since #108560 is an approved, green fix awaiting merge, it already serves as the tracking artifact, so I did not open a duplicate. If you still want a standalone issue to cross-reference the ~52 affected PRs independently of the PR lifecycle, say the word and I will open one. Agreed on the ~2026-06-25 onset (a master change to the cleaner scheduling/log-level or the upgrade-check allowlist, not the six-week-old fixture). The #108560 matchers are signature-based with no hardcoded IP, so they cover it regardless of the underlying trigger. |
Since the last master merge, master gained `04492_regexp_pattern_with_nul_byte`, which took the `04492` prefix this PR's `04492_tostartof_hour_interval_extreme_overflow` was using. Renamed both tests to the next free prefixes against `master` (what `add-test` would assign today): 04492_tostartof_hour_interval_extreme_overflow -> 04493_tostartof_hour_interval_extreme_overflow 04493_tostartof_minute_interval_extreme_overflow -> 04494_tostartof_minute_interval_extreme_overflow SQL and reference contents are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Continued work on this PR (
State: AI Review verdict is ✅ Approve, 0 unresolved review threads, and the PR is The previously-noted red jobs were all master-wide flakes unrelated to this date-arithmetic change ( |
|
Continued work on this PR ( The only non-green CI result on the current head ( This is unrelated to this PR (a I re-ran the failed shard (workflow run |
|
Continued work on this PR ( The flaky shard I re-ran in the previous update (
No source or test changes were needed. No re-merge was warranted — the merge-base is from yesterday (< 1 day old), CI is green, and |
…on on master Since the last CI run, `master` gained multiple new stateless tests at the `04493` and `04494` prefixes this PR's two tests were using (e.g. `04493_clickhouse_local_max_num_to_throw`, `04493_distributed_alias_column_reference`, `04493_jit_simple_regexp`, `04494_jit_simple_regexp_extract_replace`, `04494_qbit_stride_dot_product_transposed`), which would have left several tests sharing each prefix after merge. Renamed both tests to the next free prefixes against `master` (what `add-test` would assign today): 04493_tostartof_hour_interval_extreme_overflow -> 04498_tostartof_hour_interval_extreme_overflow 04494_tostartof_minute_interval_extreme_overflow -> 04499_tostartof_minute_interval_extreme_overflow The cross-reference comment in the MINUTE test was updated to point at the renamed HOUR test; SQL and reference contents are otherwise unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Continued work on this PR (
State: the actual fix in |
|
Continued work on this PR ( The fresh CI on the current head (
Neither failure exercises I did not re-merge, re-run, or change any code this pass (the darwin fix must land in |
|
@alexey-milovidov Already root-caused and fixed: merged PR #109001 (2026-07-01). Root cause: cases 4 and 11 ran The FAIL here is stale base. #108848's commit Since the fix is already merged, a separate tracking issue seems unnecessary, but happy to open one linking #109001 if you would like it for the record. |
|
Re-merged
The merge was conflict-free; |
LLVM Coverage ReportChanged lines: Changed C/C++ lines covered: 19/20 (95.00%) · Uncovered code |

UndefinedBehaviorSanitizerreported a signed integer overflow in the masterStress test (amd_asan_ubsan)job:The AST fuzzer mutated the regression test added in #108766 into a multi-hour
toIntervalHourvariant and reached the finaldate + timereconstruction inDateLUTImpl::toStartOfHourInterval, which overflows below the minimum ofInt64forDateTime64values far outside any valid date range.Unlike
toStartOfMinuteIntervalandroundDown(fixed in #108766),toStartOfHourIntervalhas no fast path, so even UTC goes through this computation. TheINTERVAL 1 HOURcase in the existing test short-circuits totoStartOfHourand never reached the overflowing line, so only intervals of more than one hour exercise it.The fix adds a private static helper
addSaturatingthat performs the final addition saturating at theTimeboundaries instead of overflowing. For valid in-range arguments the result is unchanged (verified by a standalone-fsanitize=undefinedequivalence check over the valid range); for out-of-range extremes (whose result is meaningless anyway) it saturates rather than invoking undefined behavior. This is a long-standing latent bug, not user-visible in release builds.A new regression test
tests/queries/0_stateless/04498_tostartof_hour_interval_extreme_overflow.sqlcovers multi-hour intervals over extremeDateTime64values (thehours != 1path the existing 04415 test missed).While addressing review feedback, a second latent issue in the same function was fixed: the hour count is only validated to be positive, so an extreme but positive
toIntervalHourmakesUInt64 seconds = hours * 3600wrap. FortoIntervalHour(4611686018427387904)it wraps to exactly0(2^62 * 3600 ≡ 0 mod 2^64), and the followingtime / secondsdivided by zero before reaching the saturated reconstruction. The conversion now detects the multiplication overflow with__builtin_mul_overflowand saturates the divisor to the maximum, so such meaningless interval counts round down to the start of the day instead of trapping. The regression test was extended with the overflowing interval-count cases.The same wrap-to-zero affected the
MINUTEsiblingtoStartOfMinuteInterval, whereInt64 divisor = 60 * minuteswraps fortoIntervalMinute(4611686018427387904)(60 * 2^62 ≡ 0 mod 2^64), androundDownToMultiplethen divided by zero (a trap on x86, undefined behavior under sanitizers; on ARM the hardware division returns zero, masking it). The minute conversion now applies the same__builtin_mul_overflowguard and saturates the divisor to the maximum ofInt64. A new regression testtests/queries/0_stateless/04499_tostartof_minute_interval_extreme_overflow.sqlcovers the overflowing interval-count cases for theMINUTEpath. The other interval siblings are safe from this exact wrap-to-zero:WEEKmultiplies by7(coprime to two), andDAY/MONTH/SECONDdivide by the raw positive interval count without a constant multiply.CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?REF=master&sha=f4c91fb747098bd4f69e0889ea3ca47f52322b06&name_0=MasterCI&name_1=Stress%20test%20%28amd_asan_ubsan%29
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
...