# DF-3983 — sbp(4): `sbp_orb_pointer` fw_asyreq-failure path completes the CCB while leaving the OCB queued, armed and owned → double xpt_done / free-list corruption

## Where
`sys/dev/disk/sbp/sbp.c:1175-1179`

```c
	if(fw_asyreq(xfer->fc, -1, xfer) != 0){
			sbp_xfer_free(xfer);
			ocb->ccb->ccb_h.status = CAM_REQ_INVALID;
			xpt_done(ocb->ccb);
	}
```

The OCB stays (a) on `sdev->ocbs` (enqueued by `sbp_enqueue_ocb`,
sbp.c:2637-2638 — it is never removed here), (b) owned: `ocb->ccb` is not
cleared, (c) armed: the CCB timeout callout reset in `sbp_enqueue_ocb`
(sbp.c:2640-2643) is never stopped, and (d) the data dmamap loaded in
`sbp_execute_ocb` is never unloaded (leak).

`fw_asyreq` fails immediately and attacker-influenceably:
- async TX queue full → `EINVAL` ("Discard a packet", firewire.c:230-235) —
  a hostile peer floods ack-busy/split transactions to keep `queued >= maxq`;
- transaction-label exhaustion → `EIO` (firewire.c:240-242, 64 tlabels
  shared bus-wide).

## Consequence
Every later completion of that ORB address completes the CCB again:
1. `sbp_timeout` fires (still armed) → `sbp_abort_all_ocbs` finds the OCB on
   the queue → `sbp_abort_ocb` (sbp.c:2684-2710): `ocb->ccb != NULL` (stale
   pointer — CAM completed the CCB and may have freed it) → **second
   `xpt_done()`** (sbp.c:2707) + `sbp_free_ocb`.
2. Or the device's real status arrives → `sbp_dequeue_ocb` matches the
   still-queued OCB → recv1 completes it again (sbp.c:1774) and frees it,
   after which the timeout path frees it once more → duplicate node on
   `free_ocbs` → two future commands share one OCB (cross-ccb corruption).

Double `xpt_done` on a CAM-completed/freed ccb is a classic CAM use-after-
free/panic; the free-list duplicate is kernel heap corruption.

## Threat model
Hostile FireWire peer sustains tlabel/AT-queue pressure (bus congestion is
trivially induced by any 1394 node) while local I/O runs against an SBP
device. Hardware-gated runtime; source-certain.

## Evidence
`VERDICT.md` — full path:line trace of both double-completion arms.
Guest has no FireWire hardware (see `../DF-3981/env.txt`) → end-to-end
status inconclusive; no runtime attempts possible beyond source analysis.

## Fix
`fix.diff` — dequeue the OCB and route through `sbp_abort_ocb`
(dmamap unload, timeout stop, single xpt_done, single free):
```c
	if (fw_asyreq(xfer->fc, -1, xfer) != 0) {
		sbp_xfer_free(xfer);
		STAILQ_REMOVE(&ocb->sdev->ocbs, ocb, sbp_ocb, ocb);
		sbp_abort_ocb(ocb, CAM_REQ_INVALID);
	}
```
