# DF-5835 VERDICT

**Status: reproduced (unprivileged, 4 successful runs: root + uid=1001,
kevent + setitimer variants). Impact: none** (Low severity — functional
corruption of long-timer semantics + kernel signed-overflow UB; no memory
corruption because callout_reset() clamps non-positive tick counts to a
minimum 1-tick timeout).

## Root cause (path:line)

- sys/kern/kern_clock.c:1198 — `} else if (sec <= INT_MAX / hz) {` followed by
  :1199 `ticks = (int)(sec * hz + howmany((u_long)usec, ustick)) + 1;`
- The guard reserves no headroom for `howmany(usec, ustick)` (≤ hz) or the +1.
  At hz=100, sec = INT_MAX/hz = 21474836 with usec ≥ 989999-ish (kevent gives
  usec=999000 → howmany=100) overflows: 2147483600 + 100 + 1 = 2147483701 →
  (int) wrap → **-2147483595**.
- Same defect: tstohz_high (kern_clock.c:1229-1230), tvtohz_low
  (kern_clock.c:1257-1258: 2147483600 + 99 = 2147483699 also overflows),
  tstohz_low (kern_clock.c:1271-1272).

## Attack path (unprivileged)

1. kevent: `EV_SET(..., EVFILT_TIMER, EV_ADD|EV_ONESHOT, 0, 21474836999, ...)` —
   kern_event.c:480-501 `filt_timerattach()` performs **no** validation of
   `kn->kn_sdata`; kern_event.c:425-433 `filt_timerreset()` computes
   `tv = {21474836 s, 999000 us}` and calls `tvtohz_high()`.
2. setitimer: `it_value = {21474836 s, 999999 us}` passes `itimerfix()`
   (normalized, non-negative) and hits `tvtohz_high()` at
   sys/kern/kern_time.c:968 inside `sys_setitimer()` (ITIMER_REAL).

## Why it is not memory corruption

sys/kern/kern_timeout.c:788-791 (`callout_reset` doc): "Callers may legally
pass any value, even if 0 or negative … a minimum timeout of 1 tick will be
enforced" — `c->rtick = sc->curticks + to_ticks` (negative → past) is clamped
to `softticks + 1` in `_callout_update_spinlocked()` (kern_timeout.c:968).
The wrapped value therefore produces an immediately-firing timer, not an OOB.

## Observed (guest, hz=100, X86_64_GENERIC 6.5-DEVELOPMENT)

- root: `[kevent] boundary data=21474836999ms: fired after 0.012494 s (n=1)`
  vs control `data=21474835999ms: n=0 after 2.010011 s (NOT fired)`.
- unprivileged (uid=1001): boundary fired after 0.015120 s; control not fired.
- setitimer: `[setitimer] boundary {21474836s,999999us}: SIGALRM after
  0.017077 s` (expected: ~249 days).

The boundary-1 control staying silent pins the overflow to exactly the
predicted sec value — not to a generic "big value" mishandling.

## Fix

fix.diff tightens all four guards to `(INT_MAX - hz - 1) / hz`.
Max-sec check at hz=100: 21474835*100 + 100 + 1 = 2147483601 ≤ INT_MAX ✔;
general: sec*hz ≤ INT_MAX-hz-1-r ⇒ +hz+1 ≤ INT_MAX-r ≤ INT_MAX ✔.
Not rebuilt on the guest (Low/functional, non-corruption) —
`fix_status: not_tested`, baseline reproduction documented above.

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
