# DF-5368 VERDICT — REPRODUCED (kernel panic, NULL-deref/UAF in ums detach window)

## Result

| item | value |
|---|---|
| status | reproduced |
| impact | panic (kernel NULL-deref / use-after-free window; DoS) |
| baseline | stock `/boot/kernel/ums.ko`, guest panicked at cycle ~12 |
| patched | fix.diff (detach reorder), 150/150 cycles clean |
| confidence | certain |

## How it was reproduced

Trigger: driver detach while the device still streams interrupt-IN
reports and an evdev client (not a `/dev/umsX` fifo) holds the device
open.

1. QEMU usb-mouse attached to piix3-usb-uhci; ums0 attaches
   ("5 buttons and [XYZ] coordinates ID=0").
2. Root process (`holdopen`) holds ums0's evdev node
   (`/dev/input/eventN`, found via `kern.evdev.input.N.name`) open.
   Open runs `evdev_register_client` → `ums_ev_open` (`ums.c:964-971`)
   → `ums_start_rx` — the intr xfer runs with `sc->sc_fifo` never
   opened.
3. Host-side monitor spam: `mouse_move` every 2 ms → ~90 reports/s
   complete; each completion runs `ums_intr_callback` (verified earlier
   by reading a stream of REL events off the node).
4. `kldunload ums` (software driver detach; the device stays attached
   and streaming — this is the only in-guest detach that keeps the
   report source alive; note `usbconfig set_config` on this stack never
   detaches the driver, and QEMU `device_del` kills deliveries ~250 ms
   before the driver detach runs, so neither works as a trigger).
5. ~12 cycles in: `Fatal trap 12`, fault VA 0x0, PC
   `lockmgr_exclusive+0x5d` (resolved against `/boot/kernel/kernel`
   symbols: lockmgr_exclusive = 0xffffffff80647b50; the faulting
   address 0xffffffff80647bad = +0x5d).

Why the PC is lockmgr and not `usb_fifo_put_bytes_max`: with
`free_q` at offset 0 of `struct usb_fifo` (`usb_dev.h:102-104`), a
NULL-fifo read would fault in `usb_fifo_put_bytes_max`/`
put_data_linear`; the observed `lockmgr(NULL)` instead matches the
SAME window's other sink — `ums_evdev_push()` (`ums.c:363`) on
`sc->sc_evdev` freed at `ums.c:776` (before the xfer drain at :780):
the freed `struct evdev_dev` gets its `ev_state_lock` field zeroed by
memory reuse, and `EVDEV_ENTER` does `lockmgr(ev_state_lock)` →
`lockmgr_exclusive(0)` → read at VA 0x0. A callback that passes
`ums_put_queue` (line 361) before the fifo pointer is NULLed reaches
exactly this.

## Proof of the window (instrumented module, stock logic + kprintf)

Markers added to ums.c (built as a module on the guest) showed, with
151 events streamed before the detach:

    UMSDIAG detach enter fpRX=0xfffff8008d3cd700
    UMSIAG detach post-fifo fpRX=0            <- fp[RX] NULLed, xfer STILL RUNNING
    UMSDIAG detach pre-evdev-free
    UMSDIAG detach post-evdev-free
    UMSDIAG detach post-unsetup                <- xfer stopped here

i.e. `usb_fifo_detach` → `evdev_free` → `usbd_transfer_unsetup` run
back-to-back with the transfer live between the first and last marker.
The µs-wide gap between "post-fifo" and the stop needs only one
delivered report to crash — hit on the 12th stock cycle.

Negative controls:
- 5× unplug cycles with nothing open (xfer never started): clean.
- dd-read of the node under spam: events flow (callback proven live).
- 170 physical unplug cycles: no panic — deliveries stop ~250 ms (hub
  poll) before ums_detach, so the window cannot be fed that way; this
  is an artifact of the emulation, not of the bug: real hardware /
  software detach paths that keep the endpoint armed (kldunload,
  driver rebind) keep the window live.

## Root cause (path:line)

- `sys/bus/u4b/input/ums.c:773` `usb_fifo_detach(&sc->sc_fifo)` first
- `sys/bus/u4b/usb_dev.c:779` early return of `usb_fifo_close` when
  `curr_cpd == NULL` (no fifo open) — xfer NOT stopped there
- `sys/bus/u4b/usb_dev.c:2031-2032` `f_sc->fp[USB_FIFO_RX] = NULL`
- `sys/bus/u4b/input/ums.c:775-777` `evdev_free(sc->sc_evdev)` +
  `sc->sc_evdev = NULL` — before the drain
- `sys/bus/u4b/input/ums.c:780` `usbd_transfer_unsetup` (the first
  point the xfer is guaranteed stopped/drained)
- sinks: `ums.c:371` (`usb_fifo_put_bytes_max(NULL)`),
  `ums.c:922` (`usb_fifo_put_data_linear(NULL)`),
  `ums.c:363`→`ums.c:937` (`evdev_push_*` on freed evdev)
- `sys/bus/u4b/usb_dev.h:103` `free_q` at offset 0 of usb_fifo
  (explains fault VA 0x0 for the fifo flavor)

## Threat model

Requires an open evdev consumer on the ums device (root or group
`input`) plus a driver detach while reports flow: physical
unplug/re-dock on real hardware where the HC keeps the endpoint armed
briefly, `kldunload ums`, module rebind, or the generic USB detach path
under the right timing. The kernel crashes (DoS); the freed-evdev
flavor is a UAF read/write window into `M_EVDEV` memory, though we did
not develop it beyond the panic (the observable crash is the read of a
zeroed lock pointer).

## Fix validation

fix.diff = reorder ums_detach (unsetup + callout drain first).
- `usbd_transfer_stop(NULL)` is NULL-safe (usb_transfer.c), so
  `ums_stop_rx` reached later from `usb_fifo_close`/`ums_ev_close`
  remains correct.
- Patched module, identical race: 150/150 cycles clean, guest up
  (fix_run.log).
- The `/dev/ums0` fifo-open detach under stream (moused holds the node
  on every attach) clean across all cycles; unload RC=0.

## Artifacts

- run.log — stock race, guest down at ~cycle 12 (ssh timeout)
- bootpanic.log / panic.txt — full serial-console panic
- fix.diff, fix_run.log — fix + 150/150 clean
- umsrace.py, race_v2.py, holdopen.c — rig

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