# Fix validation — DF-5288 (and combined with DF-5287)

## Baseline (unpatched `with-src` kernel)

| run | result |
|---|---|
| #1 (fresh boot) | `Fatal trap 12`, VA=0x130, `usbd_transfer_pending+0x3c`, guest dead in DDB (`panic.txt`) |
| #2 (fresh boot, rebuilt binary) | `kernel: type 9 trap`, same RIP, guest dead (`panic_run2.txt`) |

The PoC never survived more than ~60 s unpatched.

## Patched kernel (fix.diff applied to sys/bus/u4b/usb_generic.c)

Build: `make -j 6 nativekernel KERNCONF=X86_64_GENERIC` → BUILD_RC=0
(-Werror clean), `make installkernel` → INSTALL_RC=0, booted `#1: Fri Oct
2 05:05:50 UTC 2026` (fix_build.log).

| check | result |
|---|---|
| `./df5288s` (3000 uninit cycles) | **`survived 3000 uninit cycles`, rc=0** (`run.patched.log`) |
| `timeout 300 ./df5288` | no panic, guest alive (rc=124 = timeout kill) |
| FS functional smoke (`fssmoke`) | FS_INIT → FS_OPEN → 5× (FS_START + FS_COMPLETE, first control transfer status=0) → FS_UNINIT, all complete, no hang |

## Fix iteration note (honesty)

The FIRST generated version of the fix had a lock-balance mistake: early
`goto complete` paths in `ugen_fs_copy_out()` returned with `priv_lock`
still held, so the next ioctl panicked with
`panic: lockmgr: locking against myself` (trace: `ugen_ioctl` →
`lockmgr_exclusive` ×2) — caught by the FS smoke test during validation.
The corrected `fix.diff` in this pack releases `priv_lock` exactly once on
every exit path (`done_locked:` / guarded `complete:`) and also preserves
the original "returns holding priv_lock" contract of `ugen_transfer_setup()`
for the DF-5287 half.  The corrected kernel passed all checks above.
