β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3983

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3983 Β· 5 files
FileTypeDescriptionSize
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);
    }
VERDICT.md
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff applies cleanly; runtime validation impossible on this guest (no FireWire hardware to induce fw_asyreq failure).

fix.diff in pack
↓ fix.diffper-fix-DF-3983

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.