# DF-5308 — `usb_filter_detach()` error path leaves the dropped knote linked in the live USB fifo's klist → freed-knote walk + attacker-controlled `f_event` dispatch (use-after-free)

## What

`usb_filter_detach()` (sys/bus/u4b/usb_dev.c:1222) is the knote `f_detach`
callback the USB fifo layer installs from `usb_kqfilter()`.  It begins by
taking a device reference:

```c
err = usb_ref_device(cpd, &refs, 0 /* no uref */ );
if (err) {
        return;                     /* <-- BUG: knote stays linked */
}
...
klist = &f->selinfo.ki_note;
knote_remove(klist, kn);            /* only reached on success */
```

`usb_ref_device()` fails for several routine reasons while the fifo is still
alive — most simply because the FIFO refcount is saturated
(`f->refcount == USB_FIFO_REF_MAX`, usb_dev.c:251), or the device is in
`USB_STATE_DETACHED` (usb_dev.c:211), or the fifo slot was just delinked
(usb_dev.c:251).  When it fails, the knote is **not** removed from
`f->selinfo.ki_note` — but the caller, `knote_detach_and_drop()`
(sys/kern/kern_event.c:1764), destroys the knote immediately after `f_detach`
returns.  The freed knote remains linked in the fifo's klist.

The next `KNOTE(&f->selinfo.ki_note, 0)` — issued from `usb_fifo_close()`
(usb_dev.c:792, gated only on `f->flag_isselect`, which the early-return
fails to clear) or from `usb_fifo_wakeup()` (usb_dev.c:1784, reachable from
every USB transfer completion via `usb_fifo_put_data*`) — walks the list with
`knote()` (kern_event.c:1810): reads `kn->kn_kq`, `kn->kn_status`, and calls
`filter_event(kn, hint)` → `kn->kn_fop->f_event(kn, hint)` — **a function
pointer load and indirect call through freed, attacker-reclaimable heap
memory**, with `rdi` pointing at the (attacker-forged) knote.

## Trigger (fully unprivileged; user `maxx`, member of `operator`)

1. `fd = open("/dev/usb/0.2.1", O_RDONLY)` — ugen interrupt-IN fifo of the
   QEMU usb-mouse on EHCI (the same rig DF-5287/DF-5288 used).
2. Register EVFILT_READ knotes on `fd` from several kqueues (knote fanout).
3. Fill the per-CPU knote cache (KNOTE_CACHE_MAX = 64) so later freed knotes
   are `kfree()`d to the slab allocator and become reclaimable by any
   same-size `kmalloc`.
4. `fork()` 254 children that block in `read(fd)` — each blocked reader
   holds one `f->refcount` (usb_dev.c:1426/285), saturating it at
   `USB_FIFO_REF_MAX` (0xFF).  Marker: any further ioctl on the fd fails
   with ENXIO (`usb_ioctl` → `usb_ref_device`, usb_dev.c:1094).
5. `close()` the kqueues → `kqueue_terminate()` → `knote_detach_and_drop()`
   → `usb_filter_detach()` → `usb_ref_device()` fails (saturated refcount) →
   **early return; knotes freed while still linked**.
6. Reclaim the freed knote slots with fully controlled 136-byte blocks:
   `bind()` AF_UNIX sockets with `sa_len = 136 == sizeof(struct knote)`;
   `dup_sockaddr()` (kern/uipc_usrreq.c:1165 → kern ker) `kmalloc`s
   `sa_len` bytes and copies the user buffer verbatim.  DragonFly slab
   zones are shared per-size across malloc types, so the M_SONAME block
   lands on the freed M_KQUEUE knote.  Forged fields: `kn_next = NULL`,
   `kn_kq = 0x4343...`, `kn_status = 0`, `kn_fop = TARGET`.
7. Kill the readers (refcount drains), then `close(fd)` →
   `usb_cdevpriv_dtor` → `usb_fifo_close()` → `flag_isselect` still set →
   `KNOTE()` → `knote()` walks the forged knote → `filter_event()`.

## Observed results (with-src guest kernel, usb_dev.c md5-identical to the
audited tree)

* fault rig (`kn_fop = 0x4141414141414141`):
  `Fatal trap 9: general protection fault`,
  `Stopped at filter_event+0x4: testb $0x2,(%rax)` — the instruction is the
  `kn->kn_fop->f_flags & FILTEROP_MPSAFE` load; `%rax` is our chosen
  non-canonical `kn_fop`.  Reproduced 3× (panic.txt, panic.2.txt,
  panic.fault.txt).
* call rig (`kn_fop = &kprintf - 24`, so `f_event == kprintf`):
  `Stopped at filter_event+0x1d: ret` — the indirect `f_event` call through
  the forged `kn_fop` executed (the trap moved from the `f_flags` load to
  the post-call `ret` exactly as the forged pointer dictates) and control
  flow was corrupted.  Reproduced 2× (panic.call.txt, panic.call.1.txt).
* the run marker shows the mechanism end-to-end:
  `while saturated: ioctl = -1 ENXIO` (refcount saturated → the same failure
  `usb_filter_detach` hits) then `after drain: errno = ENOTTY` (refs
  released, dispatch reachable again).

Primitive: **arbitrary kernel function call with `rdi` = 136-byte
attacker-controlled heap block** (and an 8-byte `kn_next`/status-controlled
walk).  On this guest (no SMAP/SMEP/KASLR) this is a standard
fake-filterops/ROP step away from `uid=0` (point `kn_fop` at a controlled
kernel-address structure, pivot on `rdi`).  The chain was not completed this
session; the controlled dispatch and controlled fault address are proven.

## Fix

`fix.diff`: on the `usb_ref_device()` failure path, best-effort unlink the
knote — re-derive the fifo pointer under `usb_ref_lock` (same lookup
`usb_ref_device()` performs) and `knote_remove()` it if the fifo slot is
still populated; if the slot is NULL the fifo (and its klist) is already
gone and there is nothing to unlink.  Validated by rebuild + re-run
(fix_validation.md).

## Files

* `df5308.c`   — the trigger (fault/call modes)
* `diag.c`     — refcount-saturation diagnostic
* `build.sh`, `run.sh`
* `run.log`, `run.2.log`, `panic*.txt` — decisive runs
* `env.txt`    — guest environment
* `fix.diff`, `fix_build.log`, `fix_validation.md`
