# VERDICT — DF-5308 (usb_filter_detach knote use-after-free)

**status: reproduced** / **impact: panic (controlled function-pointer
dispatch through reclaimed kernel heap — memcorrupt; uid0 chain one
fake-filterops step away on this no-SMAP/SMEP/KASLR guest)** /
**confidence: certain**

## Root cause (line-accurate)

* `sys/bus/u4b/usb_dev.c:1239-1242` — `usb_filter_detach()` calls
  `usb_ref_device(cpd, &refs, 0)`; **on failure it returns without
  `knote_remove()`**, leaving the knote linked in the live fifo's
  `f->selinfo.ki_note` and leaving `f->flag_isselect = 1`
  (usb_dev.c:1264 is the only other clear site).
* `sys/kern/kern_event.c:1764-1775` — `knote_detach_and_drop()` calls
  `f_detach()` then `knote_drop()` → `knote_free()` → per-CPU cache or
  `kfree(kn, M_KQUEUE)` (kern_event.c:2096). The knote is destroyed even
  though the device-side klist still references it.
* `sys/bus/u4b/usb_dev.c:791-796` — `usb_fifo_close()` (also reached from
  `usb_fifo_free()` at :622 and from `usb_cdevpriv_dtor` at :969) fires
  `KNOTE(&f->selinfo.ki_note, 0)` whenever `flag_isselect` is set;
  `usb_fifo_wakeup()` (:1784) fires the same from every transfer
  completion (`usb_fifo_put_data*` / `usb_fifo_get_data*`).
* `sys/kern/kern_event.c:1810-1880` — `knote()` walks the list, reads
  `kn->kn_kq`, `kn->kn_status`, and dispatches `filter_event(kn, hint)` →
  `kn->kn_fop->f_event(kn, hint)` (kern_event.c:1789-1799) — an indirect
  call through the freed knote.  Un-reclaimed stale entries are skipped
  because the dropped note still has `KN_PROCESSING|KN_DELETING` set, so
  the crash requires (and proves) **reclamation with attacker data**.
* Failure modes of `usb_ref_device()` reachable while the fifo is alive:
  refcount saturation `f->refcount == USB_FIFO_REF_MAX` (usb_dev.c:251 —
  demonstrated), `USB_STATE_DETACHED` (usb_dev.c:211), delinked fifo slot
  (usb_dev.c:251), `curr_cpd` mismatch (usb_dev.c:253).

## How it was reproduced (unpriv `maxx`, /dev/usb/0.2.1, with-src kernel)

`df5308.c` implements: 96-kqueue knote fanout → per-CPU knote-cache fill →
254 forked readers blocking in `read()` to saturate
`f->refcount == 0xFF` (proved by `ioctl(unknown-cmd) = ENXIO` while they
sleep and `ENOTTY` after they are killed) → close the kqueues (all knotes
dropped with the early-return) → immediate AF_UNIX `bind()` spray of
136-byte (`== sizeof(struct knote)`) sockaddrs whose bytes 2..135 are
fully user-chosen (`dup_sockaddr()` kmallocs `sa_len` bytes verbatim;
DragonFly slab zones are per-size, shared across malloc types) → kill the
readers → `close(fd)` → `usb_fifo_close()` → `KNOTE()` → forged dispatch.

* fault rig (`kn_fop = 0x4141414141414141`): **3/3 panics**
  `Fatal trap 9 ... Stopped at filter_event+0x4: testb $0x2,(%rax)`
  (`panic.txt`, `panic.2.txt`, `panic.fault.txt`) — `%rax` is exactly the
  forged `kn_fop`; the trap is the `f_flags` MPSAFE load at the address we
  chose.  A non-USB path cannot produce this: `filter_event` is kqueue
  glue, and only the USB klist walk reaches it with our forged note.
* call rig (`kn_fop = &kprintf-24` → `f_event == kprintf`): **2/2 traps**
  `Stopped at filter_event+0x1d: ret` (`panic.call.txt`,
  `panic.call.1.txt`) — the trap site moved from the `f_flags` load to the
  instruction after the indirect call, exactly as the forged pointer
  dictates: the callee executed and control flow was corrupted at the
  return.  (The visible-console variant of the kprintf marker was not
  captured; the two trap-site signatures across the two rigs are the
  control proof.)
* A wedged-guest manifestation (kernel hang, no panic text) also occurred
  twice when the walk hit junk-reclaimed slots (garbage `kn_kq` pool-token
  wait / lock deadlock) — same root cause, DoS flavor.

## Exploit chain (status)

1. ✅ unprivileged dangling-knote creation (refcount saturation)
2. ✅ deterministic-ish same-slot reclamation with fully controlled
   136-byte content (AF_UNIX bind spray; knote offsets kn_next@24,
   kn_kq@48, kn_status@88, kn_fop@120, kn_hook@128 all inside sun_path)
3. ✅ controlled indirect call `kn->kn_fop->f_event(kn, 0)` (rdi = forged
   block; controlled fault address proven; call-execution proven)
4. ⬜ uid=0: needs a known-address fake `struct filterops` (no KASLR:
   static kernel addresses known; point `kn_fop` at controlled memory or
   a static address whose +24 holds a stack-pivot gadget; ROP from `rdi`).
   Not completed this session — no hard blocker, pure exploit-dev time.

## Why this is not DF-5287/DF-5288/DF-1015/DF-1016/DF-2346

* DF-1015 is a refcount *leak* on EOPNOTSUPP in `usb_kqfilter`
  (usb_dev.c:1191/1203) — different function, no memory corruption.
* DF-1016/DF-2346 are raw-`struct proc` SIGIO pointer issues — unrelated.
* DF-5287/DF-5288 live in usb_generic.c (`ugen_transfer_setup`,
  USB_FS_* ioctl races) — this finding is the *usb_dev.c detach error
  path* leaving a *kqueue* object dangling; the sinks and the objects
  differ (knote vs usb_xfer/fs_xfer).

## Fix

`fix.diff` (validated, see fix_validation.md): unlink the knote on the
`usb_ref_device()` failure path via a best-effort lookup under
`usb_ref_lock`; no-op when the fifo slot is already NULL (klist died with
the fifo).

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