# DF-5235 — MAX_SCHEDULE_TIMEOUT truncates to -1: "infinite" completion waits expire after one tick

## What this is

PoC + fix validation for the LinuxKPI completion shim
(`sys/dev/drm/linux_completion.c` +
`sys/dev/drm/include/linux/completion.h`).

`__wait_for_completion_generic()` passes its `unsigned long timeout`
argument straight into `lksleep()`'s `int timo` parameter.  The
no-timeout entry points (`wait_for_completion()`,
`wait_for_completion_interruptible()`) pass `MAX_SCHEDULE_TIMEOUT`
(`LONG_MAX`), which on x86_64 truncates to `timo = -1`.
`tsleep()` treats any nonzero timo as "arm a callout", and
`_callout_setup_quick()` clamps a non-positive tick delta to the
**next tick** — so every "wait forever" completion wait returns
after ~1 tick (10 ms at hz=100), with **success**, before the
completion ever fired.

Distinct from DF-2222 (same file): DF-2222 is the missing loop break
on ERESTART (signal → busy-spin, unkillable).  This finding is the
timeout *truncation* (no signal → premature EWOULDBLOCK → spurious
success).  Opposite failure mode, different code, different fix.

## Files

* `compbug.c`        — kernel-module PoC (self-contained, MOD_LOAD runs the test)
* `Makefile`         — kmod build against the guest's live /usr/src
* `linux_completion.c` — NOT in the pack; copied **verbatim** at build time
  from `/usr/src/sys/dev/drm/linux_completion.c`
  (sha256 `477bdd88e916bf95466e92fad95bff4355cfd0e719efa2b8c742f6aa23fd8a6e`,
  identical to the audit tree) — the *real* upstream entry points are tested.
* `linux_wait.c`     — likewise copied verbatim (only for
  `__init_waitqueue_head`; its wait_event-family helpers are dead code here,
  never executed on the completion path)
* `fix.diff`         — git-apply-able fix (validated below)
* `build.sh`, `run.sh`, `capture.sh` — exact commands
* `build.log`        — baseline (unpatched) build, full
* `build-fixed.log`  — build after applying fix.diff to the guest's /usr/src
* `run.log`, `run.2.log` — two baseline runs (unpatched): BUG both times
* `run-fixed.log`    — run after fix: waits block until the real completion
* `console.txt`      — serial console history of every load (A/B/A story)
* `env.txt`          — guest uname/hz/compiler/kldstat
* `SHA256SUMS`       — guest-side hashes of sources + .ko

## Build

On the guest (as root), with kernel source at /usr/src:

    cd /root/df5235 && make

## Run

    kldload ./compbug.ko && sleep 13 && dmesg | grep compbug | tail

## Expected output

Baseline (unpatched kernel, verified 2 independent loads):

    compbug[1] interruptible: ret=0 elapsed=1 ticks (completer due at 500): BUG: returned before completion
    compbug[2] wait_for_completion: elapsed=1 ticks (completer due at 500): BUG: returned before completion
    compbug[3] control timeout=10: ret=0 elapsed=9|10 ticks: OK
    compbug: hz=100, LONG_MAX=9223372036854775807 -> int timo=-1 (LKSLEEP SEES -1)

Case 1 is `wait_for_completion_interruptible()` — the exact entry point used
by `drm_atomic_helper.c:2627,2638,2649` and `i915/intel_psr.c:869` — returning
0 (== success in the Linux contract) after **one tick** although the
completer was scheduled 500 ticks (5 s) out.

Case 3 is the control: a small in-range timeout (10 ticks) behaves exactly
per contract (ret=0 after ~10 ticks), proving the generic timeout machinery
is fine and only the `MAX_SCHEDULE_TIMEOUT` conversion is broken.

With `fix.diff` applied to /usr/src (run-fixed.log):

    compbug[1] interruptible: ret=0 elapsed=499 ticks (completer due at 500): OK
    compbug[2] wait_for_completion: elapsed=499 ticks (completer due at 500): OK
    compbug[3] control timeout=10: ret=0 elapsed=9 ticks: OK

## Notes

* `run.log`'s first three lines are stale ring-buffer lines from the
  preceding patched-module load (the load order was patched → baseline);
  the six decisive baseline lines are the tail.  `run.2.log` and
  `console.txt` show the clean sequence.
* No panic, no corruption: the demonstrated impact is the broken
  synchronization primitive itself (spurious early success).  In-tree
  consumers are DRM (i915/amdgpu/radeon), i.e. hardware-gated; on this
  QEMU guest none probe, hence impact is recorded as "none" with the race
  consequences described in the finding.
