# DF-5298 — umass CBI: CCB leaked when the device detaches while a command is in the control phase

## What

For CBI (Control/Bulk/Interrupt) wire protocol, the SCSI command travels as
a class ADSC **control transfer**.  `umass_t_cbi_command_callback()`'s error
branch (`sys/bus/u4b/storage/umass.c:1864-1882`) bypasses
`umass_cancel_ccb()` and does its own completion:

```c
default:			/* Error */
	if ((error == USB_ERR_STALLED) ||
	    (sc->sc_transfer.callback == &umass_cam_cb)) {
		sc->sc_transfer.ccb = NULL;		       /* umass.c:1873 */
		(sc->sc_transfer.callback)
		    (sc, ccb, sc->sc_transfer.data_len,       /* umass.c:1874-1876 */
		    STATUS_CMD_UNKNOWN);
	} else { ... umass_tr_error(xfer, error); ... }
```

`umass_cam_cb(STATUS_CMD_UNKNOWN)` answers by *starting the autosense
fetch* (`umass_command_start`, umass.c:2607-2611).  If this happens while
the driver is being torn down — i.e. the CANCELLED callback fires from
inside `usbd_transfer_unsetup()` (usb_transfer.c:1367-1440 clears
`sc->sc_xfer[]` at line 1405 *before* draining the in-flight transfer at
line 1410) — then `umass_command_start()` finds
`sc->sc_xfer[sc->sc_last_xfer_index] == NULL` and falls back to
`umass_cancel_ccb()` (umass.c:1626-1630), which is a no-op because the CCB
pointer was already NULLed at line 1873.  **The CCB is never `xpt_done()`d
and never freed**: the issuing thread (e.g. an unkillable
`biowait`/`getccb` waiter reading the disk) sleeps forever and a kernel CCB
leaks.

Contrast with the BBB protocol: every BBB error path funnels through
`umass_tr_error` → `umass_cancel_ccb`, which completes the CCB exactly
once; BBB detach cannot leak.  The CBI command-phase branch is the odd one
out — it completes the CCB *optimistically* by assuming a sense fetch is
still possible.

## Trigger (threat model)

Evil CBI USB device (the device chooses its own descriptors —
class 08 / subclass 06 / protocol 00): serve the CAM probe normally so the
disk comes up, then, on a victim READ(10), stop answering the ADSC control
transfer (the umass CBI command xfer — 5 s timeout — is now in flight,
`sc->sc_transfer.ccb` = the victim's CCB, `callback == umass_cam_cb`), and
physically disconnect (a $2 MCU can drop its D+ pullup — the classic
BadUSB self-unplug).  On real hardware the control transfer is still
pending when `uhub` detaches the driver, so the CANCELLED callback fires
from inside `usbd_transfer_unsetup()` with `sc->sc_xfer[]` already NULL —
the leak path above.

## Verification status (honest)

Source-proven path; **not reproduced at runtime on the QEMU rig**:

The pack contains a full CBI-mode evil device (`pyevilmsc.py … cbi R3 0` —
hang the 4th READ(10) ADSC forever) plus a guest-side cable pull
(`guest-pull-cable.sh` = `usbconfig -d 0.2 power_off`).  Two detach
variants were exercised:

1. usbredir `device_disconnect` (evil self-unplug emulation): QEMU's
   `usb-redir` *eagerly cancels* all in-flight transfers when the redirect
   peer disconnects, so the ADSC errors (USB_RET_IOERROR) *before*
   `umass_detach()` runs; the sense-fetch path then runs with the transfer
   array still alive, the sense fetch fails on the dead device,
   `umass_tr_error` → `umass_cancel_ccb` completes the CCB — the victim
   `dd` exits with `Input/output error` after exactly the hang delay
   (1.207 s; see `evilmsc-cbi-disconnect.log`).
2. guest-side `power_off` (cable pull emulation) with the ADSC hung
   forever: same outcome — QEMU's UHCI cancels the pending transfer at
   port-off; `dd` exits with I/O error (see `evilmsc-cbi.log` +
   `guest-pull-cable.sh`).

Both runs *confirm the driver takes the umass_cam_cb(UNKNOWN) sense-fetch
detour on CBI command-phase errors* (dmesg: `(da8:umass-sim0:0:0:0):
AutoSense Failed`), but QEMU's USB emulation never leaves the control
transfer pending *into* `usbd_transfer_unsetup()` the way a real cable yank
does (real HC hardware keeps the TD queue active on a vanished device; the
stack only sees the disconnect via the hub port change).  The leak
window — CANCELLED delivered with `sc->sc_xfer[]` already NULL — is
therefore not reachable through usbredir/UHCI emulation, and the finding is
filed on the strength of the code trace with runtime evidence of every
step except the final teardown interleaving.

## Files

* `pyevilmsc.py` — evil USB device emulator (BBB + CBI modes; `cbi R3 0`
  = hang the 4th READ(10) forever).
* `guest-pull.sh` — guest-side port power-off (cable pull).
* `evilmsc-cbi.log`, `evilmsc-cbi-disconnect.log` — device-side traces of
  both detach variants.
