# DF-5330 — VERDICT

**Finding:** `resettodr()` (sys/platform/pc64/isa/clock.c:1148) converts the
wall clock to RTC year/month/day using `unsigned long tm = tv.tv_sec`
(clock.c:1151,1158-1159) followed by an unbounded year-subtraction loop
(clock.c:1175-1178) executed under `crit_enter()` (clock.c:1161). A wall
clock set before 1970-01-01 — or any clock pushed before the epoch by the
`tm -= tz.tz_minuteswest * 60 + (wall_cmos_clock ? adjkerntz : 0)`
adjustment (clock.c:1167) — wraps `tm` to ~1.8e19, so the loop needs
~5e16 iterations to terminate. It never does in practice: the spinning CPU
has preemption/interrupts deferred (crit) and the caller
(`settime()`, kern/kern_time.c:127) holds `masterclock_lock` exclusively
across the whole `resettodr()` call (kern/kern_time.c:167). **Permanent,
unkillable kernel hang; no panic, no crash dump; the guest cannot even be
shut down** (the shutdown eventhandler calls `resettodr()` again,
clock.c:1054/1063).

## Reproduced? YES (baseline, vulnerable kernel)

Chain proven live on the QEMU guest (fresh `with-src` snapshot, kernel of
2026-07-02, `kern.securelevel=-1`):

1. `settimeofday({tv_sec:-1})` as root → `sys_settimeofday()`
   (kern/kern_time.c:655; `caps_priv_check_self(SYSCAP_NOSETTIME)` passes)
   → `settime()` (kern/kern_time.c:127) → `set_timeofday()` →
   **synchronous** `resettodr()` (kern/kern_time.c:167).
2. `resettodr()`: `microtime(&tv)` now returns the pre-epoch clock;
   `tm = (unsigned long)-1`; the year loop at clock.c:1175 spins inside
   crit.
3. Observed (run.log): the PoC prints only
   `DF-5330: setting wall clock to tv_sec=-1 ...`; `settimeofday()` never
   returns (no RETURNED marker, ever). The 90 s observation session times
   out (RUN_RC=124). A follow-up session that runs `ps axl | grep df5330`
   also times out — process-table inspection needs resources gated by the
   dead CPU. `kill -9` cannot be delivered (the thread never leaves crit).
   Simple `echo` on other CPUs still works: the system is half-frozen and
   useless, **not** panicked (dmesg/serial console show no crash).
4. Recovery required `kill -9` of the QEMU process on the host: the guest
   ignored `shutdown` (which would have entered the same spin from
   `resettodr_on_shutdown`).

Classification per the honest-status table: RUN_RC=124 + guest wedged,
hang expected → **status=reproduced, impact=dos** (privileged local DoS;
severity Low because the trigger requires root / SYSCAP_NOSETTIME).

## Why not higher severity?

The only entry points that reach `resettodr()` with attacker-influenced
time are `settimeofday()`/`clock_settime()` (both
`caps_priv_check_self(SYSCAP_NOSETTIME)`-gated, kern_time.c:661/753) and
system shutdown (privileged). No unprivileged path was found. Impact for a
privileged attacker is still notable because the hang is silent, total and
dump-less — but by the project rubric this is Low.

## Fix validation (fix.diff) — FIXED

`fix.diff` adds a range check after the local-time subtraction
(clock.c:1168+) that bails out (restoring `rtc_statusb` and releasing crit)
when `tm` exceeds a sane bound (year 9999), which also covers the wrapped
pre-epoch case. Validated on the guest:

1. Applied `fix.diff` to the guest's `/usr/src`, built
   `make -j6 nativekernel KERNCONF=X86_64_GENERIC`, installed, rebooted
   into the patched kernel.
2. Re-ran the exact same PoC: `settimeofday()` **returns immediately**
   (`settimeofday RETURNED (bug did not trigger)` printed), the guest
   stays healthy, follow-up `ps` works, RTC left untouched.
3. Also re-checked normal RTC write path still works (set clock forward,
   `resettodr` writes RTC, value in range) — no regression.

See run.2.log for the patched run.

## Negative results / adjacent candidates considered and killed

- resettodr month loop (clock.c:1185 `for (m = 0; ; m++)`) has no `m < 12`
  bound, but the preceding year loop guarantees `tm <= 365` on entry, so
  `m` never exceeds 11 in the current code (the fix's range guard makes
  this airtight). Not independently exploitable.
- inittodr month OOB (`daysinmonth[month-1]`, clock.c:1113) = known
  DF-1027, not re-reported. Unvalidated RTC day/hours/min/sec only skew
  the clock (no memory unsafety).
- `timer0_running` (clock.c:220 vs 359): clkintr clears it without
  clock_lock while other CPUs read it under the lock — analyzed: the
  interrupt handler unconditionally re-dispatches all per-CPU systimer
  queues, so a skipped reload self-heals before the timer can be left
  dead. 15 years in production. Not reportable as demonstrated.
- `hw_i8254_timestamp` (clock.c:1370): `%016lx %016lx` needs 34 bytes in
  `char buf[32]` — ksnprintf truncates safely; cosmetic only.
- `sysbeep()` leaks timer2 acquisition when `sysbeep_enable == 0`
  (clock.c:507-510 returns without `release_timer2()`): console-beep-only
  logic bug, no security impact.
- All sysctls in the file are CTLFLAG_RD; tunables are loader-time only.
  `hw.i8254.timestamp` is world-readable but leaks only counter values
  (TSC is user-readable anyway).
- muldivu64 (systm.h:505) uses 128-bit intermediates — no overflow in
  DELAY/CHECKTIMEOUT/i8254 math for any in-tree caller.

## 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.
