# DF-5235 — VERDICT

**Status: reproduced** (on-guest, two independent baseline loads; fix validated A/B)
**Impact: none *demonstrated on this guest*** (primitive = broken synchronization:
"infinite" kernel completion waits return spurious success after 1 tick;
in-tree consumers are DRM drivers, hardware-gated, absent on this guest)
**Confidence: certain** (code defect proven by execution; consumers traced)

## Root cause (path:line)

* `sys/dev/drm/include/linux/completion.h:79-116` —
  `__wait_for_completion_generic(struct completion *c, unsigned long timeout, int flags)`
  passes `timeout` **unchanged** to `lksleep()` at completion.h:90.
* `sys/sys/systm.h:414` — `int lksleep(..., const char *, int timo)`: the
  timeout parameter is an **int**.
* `sys/dev/drm/include/linux/sched.h:78` — `#define MAX_SCHEDULE_TIMEOUT LONG_MAX`
  = `0x7FFFFFFFFFFFFFFF` on x86_64 → truncates to `timo = -1`
  (implementation-defined C conversion; gcc/clang modulo → 0xFFFFFFFF).
* `sys/dev/drm/linux_completion.c:33` and `:40` — the two no-timeout entry
  points pass `MAX_SCHEDULE_TIMEOUT`.
* `sys/kern/kern_synch.c:691-693` — `if (timo)` is TRUE for -1 →
  `_callout_setup_quick(..., timo=-1, endtsleep, td)`.
* `sys/kern/kern_timeout.c:971-972` —
  `c->qtick = sc->curticks + ticks;` → `curticks-1`;
  `if (c->qtick - sc->softticks <= 0) c->qtick = sc->softticks + 1;`
  → the callout fires on the **next tick**.
* `endtsleep` (kern_synch.c:935) sets TDF_TIMEOUT → `tsleep` returns
  `EWOULDBLOCK` → completion.h:92-95 sets `timeout_expired=true, ret=0` →
  loop exits → `wait_for_completion_interruptible` maps that to **0 = success**
  (linux_completion.c:45).

Contrast with the tree's own correct conversion in
`sys/dev/drm/include/linux/sched.h:130-133` (`schedule_timeout`):
`timo = timeout >= INT_MAX || timeout == MAX_SCHEDULE_TIMEOUT ? 0 : timeout`.

## What was executed

Kernel module `compbug.ko` built against the guest's live `/usr/src`,
linking the **verbatim** upstream `linux_completion.c`
(sha256 `477bdd88e916bf95466e92fad95bff4355cfd0e719efa2b8c742f6aa23fd8a6e`,
byte-identical to the audit tree — see SHA256SUMS) and the verbatim
`linux_wait.c` (needed only for `__init_waitqueue_head`; its wait_event
helpers are never executed on the completion path — linkage stubs satisfy
the `current`-macro symbols). See compbug.c header comment.

Three cases per load (completer scheduled 500 ticks = 5 s out):

1. `wait_for_completion_interruptible()` — **elapsed=1 tick, ret=0** → spurious
   success. This is the exact entry point used by
   `sys/dev/drm/drm_atomic_helper.c:2627,2638,2649`
   (drm_atomic_helper_wait_for_dependencies / wait_for_vblanks family) and
   `sys/dev/drm/i915/intel_psr.c:869` (psr_wait_for_idle).
2. `wait_for_completion()` — **elapsed=1 tick** (no in-tree caller today;
   latent trap, e.g. any ported driver that waits indefinitely).
3. Control `wait_for_completion_timeout(c, 10)` — ret=0 after 9-10 ticks:
   the timeout machinery is correct for in-range values; only the
   MAX_SCHEDULE_TIMEOUT conversion is broken.

Reproduced twice from independent loads (`run.log`, `run.2.log`, full story
in `console.txt`). hz=100 confirmed (env.txt), so 1 tick = 10 ms.

## Why it matters (threat)

Every DRM commit that waits for `hw_done`/`cleanup_done` "indefinitely"
proceeds after 10 ms whether or not the hardware/atomic-commit worker has
signaled completion. Linux semantics: block until complete(); return
`-ERESTARTSYS` on signal. DragonFly actual: return success after 10 ms.
Downstream consequence class: use of an object whose hardware state is not
yet quiesced (plane update on an unflipped CRTC, PSR exit before idle,
cleanup before the last vblank) — racy malfunction to potential UAF on
real GPU hardware. Not triggerable on this guest (QEMU, no i915/amdgpu
device; DRM modules not loaded — env.txt kldstat), hence impact recorded
as "none" rather than dos/corruption. No unprivileged trigger path exists
without the hardware.

## Relationship to DF-2222 (do not merge)

DF-2222 = completion.h:91-102 switch never breaks out of the loop on
ERESTART → with PCATCH and a *pending signal*, busy-spin forever.
DF-5235 (this) = completion.h:90 passes LONG_MAX into an int → *without*
any signal, premature EWOULDBLOCK after 1 tick → spurious success.
With a signal pending, tsleep's CURSIG short-circuit (kern_synch.c:622-630)
returns before the callout is armed, so DF-2222 dominates; without a
signal, the truncation dominates. Opposite failure modes, independent
lines, independent fixes; fixing one does not fix the other.

## Fix validation (A/B, same guest, same module)

1. Baseline: pristine `/usr/src`, `make` → `kldload` → `run.log`/`run.2.log`:
   elapsed=1, BUG (both loads).
2. Apply `fix.diff` to the guest's `/usr/src` copy
   (`sys/dev/drm/include/linux/completion.h`: convert
   `timeout >= INT_MAX → timo 0` exactly like `schedule_timeout`),
   `make clean && make` (build-fixed.log) → `kldload` → `run-fixed.log`:
   **elapsed=499 ticks, waits block until the real completer fires, ret=0
   (true success)**; control case unchanged (9 ticks).
3. Reverted the guest's `/usr/src` header to pristine afterwards
   (verified: `lksleep(&c->wait... timeout)` at line 90 again).

fix_status: **fixed** — baseline behavior (premature return) gone;
semantics now match Linux for both entry points; explicit-timeout path
untouched.

## Kernel references

* sys/dev/drm/include/linux/completion.h:79-116 (helper), :90 (lksleep call)
* sys/dev/drm/linux_completion.c:30-46 (both entry points)
* sys/dev/drm/include/linux/sched.h:78, 130-133 (MAX_SCHEDULE_TIMEOUT, correct conversion)
* sys/sys/systm.h:414 (lksleep int timo)
* sys/kern/kern_synch.c:691-693 (timo!=0 arms callout), :935 (endtsleep)
* sys/kern/kern_timeout.c:940-975 (_callout_setup_quick; clamp at 971-972)
* Consumers: sys/dev/drm/drm_atomic_helper.c:2627,2638,2649;
  sys/dev/drm/i915/intel_psr.c:869

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