# DF-5298 verdict

**Status: NOT REPRODUCED at runtime** (source-proven code path; the QEMU
USB emulation cannot produce the required teardown interleaving).
**Claimed impact: local DoS** — permanent unkillable D-state process plus a
leaked kernel CCB, triggered by an evil CBI USB device.
**Confidence: likely** (every step of the path is source-verified and most
were runtime-confirmed; the final interleaving is emulation-blocked).

## Why the code path is real

1. CBI commands travel as ADSC **control transfers**; the in-flight command
   keeps `sc->sc_transfer.ccb` set and
   `sc->sc_transfer.callback == &umass_cam_cb`.
2. On ANY error — including `USB_ERR_CANCELLED` —
   `umass_t_cbi_command_callback()` takes the branch at
   `umass.c:1871-1876` (`(error == USB_ERR_STALLED) ||
   (callback == &umass_cam_cb)`), NULLs the CCB pointer, and calls
   `umass_cam_cb(..., STATUS_CMD_UNKNOWN)`.
3. `umass_cam_cb(UNKNOWN)` starts the autosense fetch
   (`umass_command_start`, umass.c:2607-2611).
4. During device teardown, `usbd_transfer_unsetup()`
   (usb_transfer.c:1378-1439) clears `sc->sc_xfer[n]` at line 1405
   **before** draining the still-pending transfer at line 1410, so the
   CANCELLED callback runs with the xfer array already NULL.
5. `umass_command_start()` then hits its NULL fallback
   (umass.c:1626-1630) → `umass_cancel_ccb()` — which is a no-op because
   step 2 already NULLed `sc->sc_transfer.ccb`.
6. The CCB is never `xpt_done()`d: the issuing thread sleeps forever
   (uninterruptible `biowait` for disk I/O) and the `union ccb` is never
   freed.

BBB is immune: every BBB error path funnels through `umass_tr_error` →
`umass_cancel_ccb`, which completes the CCB exactly once. The CBI
command-phase branch is the only place that "completes" a CCB by assuming
a further transfer is possible.

## What was runtime-tested (and what it proved)

Rig: `pyevilmsc.py` in CBI mode (`cbi R3 0`) presents a CBI SCSI disk
(proto 0x00, no CCI interrupt endpoint — legal), serves the whole CAM
probe (sense answers carry the previous command's status so the probe
finishes), then **hangs the ADSC carrying dd's first READ(10) forever**.
dd (pid observed in D-state) has its CCB in flight as required by step 1.
Two detach mechanisms were then exercised:

* **usbredir `device_disconnect`** (evil-device self-unplug): QEMU's
  usb-redir cancels all in-flight transfers the moment the peer
  disconnects, so the ADSC errors *before* `umass_detach()`. The driver
  still took the step-2 detour (dmesg: `(da8:umass-sim0:0:0:0):
  AutoSense Failed` — proving the sense-fetch path ran), the fetch failed
  on the dead device, `umass_tr_error` → `umass_cancel_ccb` completed the
  CCB, and dd exited with `Input/output error` after exactly the 1.2 s
  hang delay.
* **guest-side `usbconfig -d 0.2 power_off`** (cable pull): same outcome —
  QEMU's UHCI cancels the pending transfer at port-off, dd exits with
  I/O error, device detaches cleanly.

Both runs confirm steps 1-3 of the chain at runtime. The missing
ingredient is step 4's *interleaving*: on real hardware a cable yank does
NOT abort the host-controller TD queue — the control transfer stays
pending until `usbd_transfer_unsetup()` drains it, which is exactly when
the CANCELLED callback meets a NULLed xfer array. QEMU's device model
always aborts eagerly on disconnect/port-off, so the window cannot be
produced on this rig. This is an **emulation limitation, not a refutation**
— nothing in the two runs contradicts the code trace.

## Recommended fix

In the CBI command error branch, complete the CCB unconditionally through
the `umass_cancel_ccb()` path (status `STATUS_WIRE_FAILED`) instead of
entering the sense fetch when the transport is being torn down, or guard
the `umass_cam_cb(STATUS_CMD_UNKNOWN)` call by verifying
`sc->sc_xfer[UMASS_T_CBI_COMMAND] != NULL` first.

## Fix validation

`fix_status: not_tested` — with no runtime manifestation on this rig there
is no observable to validate against; the fix is argued from the code
path.

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
