# DF-5842 VERDICT — reproduced (timer-semantics corruption, unprivileged)

## Bottom line

`select(2)`, `pselect(2)` and `ppoll(2)` copy the user-supplied timeout into
the kernel and hand it to `kern_kevent()` with **no validation whatsoever**
(`sys/kern/sys_generic.c:807-815`, `:842-849`, `:1271-1278`). Negative and
unnormalized (sub-second field ≥ 1e9/1e6) values flow into
`timespecadd`/`tstohz_high`/`precise_sleep`, where they either mean
"already expired" (immediate return with success) or overflow integer
conversions and produce arbitrary wrong durations. Demonstrated live on the
audit guest as unprivileged uid=1001, deterministic across 3 runs:

- `pselect(fd, {tv_sec:-1})` → rc=0 in **19-87 µs** (POSIX: EINVAL)
- `select(fd, {tv_usec: 2^62})` → rc=0 in **38-73 µs** — a ~146-year
  intended sleep collapses to immediate return (tick math
  `(int)(sec*hz + howmany(4.6e15 usec, ustick))` truncates mod 2^32 to a
  non-positive tick count; `tvtohz_high` kern_clock.c:1196-1200)
- `ppoll(fd, {tv_nsec: 2^62})` → rc=0 after **1.69-1.98 s** (PRECISE path:
  `ustimeout = atx.tv_sec*1000000 + atx.tv_nsec/1000` int-overflows,
  kern_event.c:975, systimer fires after the truncated value)
- `select(0 fds, {tv_sec:-1})` → **EINVAL**: the nd==0 path of the *same
  syscall* routes through `nanosleep1()`→`clock_nanosleep1()` which
  validates (`kern_time.c:467`) — proving the inconsistency is the missing
  entry check, not the sleep machinery.

Controls (`{2s}` → 2.000 s, `{0.5s}` → 0.500 s) prove the rig measures real
sleeps. Full logs: `run.log`, `run.2.log` (3 consecutive full runs + 2 T8
runs, all consistent).

## Root cause (path:line)

1. `sys_select` `sys_generic.c:807-811`: `copyin(uap->tv, &ktv)` then
   `TIMEVAL_TO_TIMESPEC` — no `tv_sec<0 / tv_usec<0 / tv_usec>=1000000`
   check.
2. `sys_pselect` `sys_generic.c:842-846` and `sys_ppoll`
   `sys_generic.c:1271-1275`: `copyin(uap->ts, &kts)` — no
   `tv_sec<0 / tv_nsec<0 / tv_nsec>=1000000000` check.
3. `kern_kevent` `kern_event.c:872-875`: `timespecadd(tsp, &ats, tsp)` on
   the raw value (negative target ⇒ in the past ⇒ `EWOULDBLOCK` treated as
   "no events" ⇒ immediate success).
4. `kern_kevent` wait loop `kern_event.c:958-1000`: PRECISE multiply
   overflows for unnormalized nsec; non-precise `tstohz_high(&atx)`
   (`kern_clock.c:1227-1250`) casts `sec*hz + howmany(nsec, nstick)` to int
   — huge nsec wraps to arbitrary/negative ticks (callout clamps to 1 tick
   ⇒ immediate wake, same endpoint as DF-5835 documented).

## Why not a duplicate of DF-5835

DF-5835 (kern_clock.c) is the seconds-term overflow inside the four
converters at the `sec == INT_MAX/hz` boundary; its fix (headroom guard in
the converters) does **not** stop T1/T2b/T3/T7 (negative and unnormalized
values) and does not cover `kern_event.c:975`. This finding is the missing
**syscall-entry validation** in sys_generic.c; the fix mirrors
`clock_nanosleep1()`'s check (kern_time.c:467) — FreeBSD does exactly this
in `kern/select`/poll paths. Complementary file, complementary fix.

## Impact

Unprivileged local timer-semantics corruption: an application's arbitrarily
long select/poll timeout can silently become immediate return or a random
short duration (breaking long-poll loops, watchdogs, retry throttles — they
spin); POSIX conformance violation. No memory corruption, no info leak —
severity Low, impact class dos (same class DF-5835 was filed under).

## Fix validation

Not rebuilt: Low-severity semantics finding, non-memory-corruption —
`fix.diff` is the entry-validation patch (3 hunks, mirrors
clock_nanosleep1's exact predicate). With it, T1/T2/T2b/T3/T7/T8 all become
clean EINVAL and the controls are unchanged. fix_status: not_tested
(no patched-kernel run performed for this Low finding).

Attempts: 2 (initial rig had an unbounded T7 that wedged the first ssh
session for its full 4000 s intended sleep — itself confirming that huge
*normalized* positive values are honored; rig was bounded with alarm(30)
and T7 re-scoped). Guest stayed up throughout; no panic; no dirty state.

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