sbp_orb_pointer fw_asyreq-failure branch xpt_done()s the CCB while leaving the OCB queued on sdev->ocbs, timeout-armed and dmamap-loaded β later timeout/device completion fires a second xpt_done on the stale ccb and/or duplicate-frees the OCB
Summary
The error path completes the CCB but does not dequeue the OCB, clear ocb->ccb, stop the CCB timeout, or unload the dmamap. fw_asyreq fails immediately and attacker-influenceably (AT-queue full EINVAL or tlabel exhaustion EIO, both sustainable by a hostile 1394 peer). The timeout arm or the device arm then completes the CCB a second time; if both run, the OCB is inserted into free_ocbs twice -> two future commands share one OCB. Fix routes the branch through STAILQ_REMOVE + sbp_abort_ocb.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3983 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 3.6 KB | β raw | |
| fix.diff | β | 585 B | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| verdict.json | β | 3.0 KB | view raw |
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
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):
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);
}
DF-3983 VERDICT β sbp_orb_pointer error path: xpt_done without dequeue/disown/timeout-stop
Status: inconclusive (hardware-gated β no FireWire controller on the audit guest, see ../DF-3981/env.txt). Source-level certainty: certain. No runtime attempt possible: the path requires sbp to be attached to a live FireWire bus with a hostile peer flooding the async queues.
Trace
Arm:
- XPT_SCSI_IO β sbp_get_ocb β bus_dmamap_load β sbp_execute_ocb
(sbp.c:2497) β sbp_enqueue_ocb puts the OCB on sdev->ocbs
(sbp.c:2637-2638) and arms the CCB timeout callout (sbp.c:2640-2643,
sbp_timeout) β sbp_orb_pointer(sdev, ocb) (sbp.c:2550-2553 or 2602).
Fail:
- sbp_orb_pointer (sbp.c:1145-1180): fw_asyreq != 0 β frees only the
xfer; sets ocb->ccb->ccb_h.status and xpt_done(ocb->ccb)
(sbp.c:1177-1178) while the OCB is still (1) linked on sdev->ocbs,
(2) has ocb->ccb set, (3) has its CCB timeout callout armed,
(4) has a loaded data dmamap.
Attacker influence on fw_asyreq failure (firewire.c:182-253):
- !(xferq->queued < xferq->maxq) β EINVAL (firewire.c:230-235):
async-response/queue saturation β sustained by any hostile 1394 node via
split-transaction/ack-busy pressure;
- fw_get_tlabel == -1 β EIO (firewire.c:240-242): the 64 tlabels are
bus-global and trivially held by a hostile peer (also the subject of
known DF-1021: M_WAITOK inside crit on the same path).
Double-completion arms (either one fires later):
1. Timeout arm: sbp_timeout (sbp.c:2148-2182) fires on the still-armed
callout β case 1 β sbp_abort_all_ocbs (sbp.c:2164) walks the queue the
OCB is still on β sbp_abort_ocb (sbp.c:2684-2710):
ocb->ccb != NULL (stale, CAM already completed it at sbp.c:1178 and
may have freed the ccb) β callout_stop + xpt_done(ocb->ccb)
again (sbp.c:2705-2707) + sbp_free_ocb. Double xpt_done of a
completed/freed CCB β CAM UAF/panic.
2. Device arm: the target device eventually answers the ORB (it may
have received it before the tlabel/queue failure, or answers a retry):
sbp_recv1 β sbp_dequeue_ocb matches the still-queued OCB by address
(sbp.c:2584-2599) β recv1 completes the CCB a second time
(sbp.c:1748-1775) and sbp_free_ocb (sbp.c:1782); the timeout arm then
frees it again β the same OCB node is inserted into free_ocbs twice β
STAILQ corruption β two subsequent sbp_get_ocb calls return the same
OCB for two different CCBs (cross-ccb status/data corruption, further
double-dones).
Additionally the loaded dmamap is never unloaded on this path (leak of the mapping/references held by bus_dmamap_load for the CCB data buffer).
Severity rationale
Requires an attached SBP device + hostile-bus conditions (or even benign congestion); impact is kernel memory corruption (double completion / free-list duplication). Medium under the audit rubric (device-gated local corruption), consistent with DF-2515/DF-2516 ratings.
Fix validation
Not possible on this guest (no FireWire hardware; fw_asyreq failure cannot
be induced without a bus). fix.diff routes the error branch through
dequeue + sbp_abort_ocb, which performs exactly-once: dmamap
sync/unload, callout_stop_async, xpt_done, sbp_free_ocb.
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.
Fix verification
not_testablefix.diff applies cleanly; runtime validation impossible on this guest (no FireWire hardware to induce fw_asyreq failure).
fix.diff in pack
Confirmed kernel references
Detail
Exploit chain
hostile 1394 peer saturates tlabels/AT queue while local I/O runs on an SBP device -> fw_asyreq(EINVAL/EIO) at sbp.c:1175 -> premature xpt_done; OCB stays queued+armed -> device answers or timeout fires -> second xpt_done (CAM UAF) and/or double sbp_free_ocb -> free_ocbs cycle -> two commands share an OCB -> cross-ccb corruption.
Evidence (decisive lines)
VERDICT.md (both double-completion arms traced path:line); fix.diff (dequeue + sbp_abort_ocb exactly-once routing, patch --dry-run clean)
PoC changes
No PoC: requires FireWire hardware absent from the guest; fw_asyreq immediate-failure paths verified in firewire.c source instead.
Verified recommended fix
In sbp_orb_pointer's fw_asyreq failure branch, STAILQ_REMOVE the ocb from sdev->ocbs and complete via sbp_abort_ocb (dmamap unload + timeout stop + single xpt_done + single free) - fix.diff.
Verdict
sbp_orb_pointer()'s fw_asyreq failure branch (sbp.c:1175-1179) completes the CCB (xpt_done) but leaves the OCB linked on sdev->ocbs, leaves ocb->ccb set, leaves the CCB timeout callout armed (sbp.c:2640-2643) and the data dmamap loaded. fw_asyreq fails immediately and attacker-influenceably (AT queue full EINVAL firewire.c:230-235; tlabel exhaustion EIO firewire.c:240-242 - both sustainable by a hostile 1394 peer via split-transaction/ack-busy pressure). Every later completion of that ORB then completes the CCB a second time: the timeout arm (sbp_timeout -> sbp_abort_all_ocbs -> sbp_abort_ocb, xpt_done at sbp.c:2707 on the stale ocb->ccb) or the device arm (sbp_dequeue_ocb still matches the queued OCB; recv1 xpt_done at sbp.c:1774) - double xpt_done on a CAM-completed/freed ccb, plus a duplicate free_ocbs insertion when both arms free the same OCB, which makes two future commands share one OCB. Source-certain; end-to-end inconclusive because the audit guest has no FireWire controller (../DF-3981/env.txt) so the path cannot be entered.
No comments yet.