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

dpt_intr never validates the status packet's target CCB is in flight: duplicate/unsolicited device-fed ccb_busaddr -> double free-list insertion of dccb + double xpt_done of stale/recycled ccb (UAF family)

Summary

sp->ccb_busaddr is controller-DMA'd; the only validation is a range check, and any in-range busaddr maps to a dccb with no DCCB_ACTIVE check. A duplicate/fabricated packet re-completes a freed dccb: callout_stop on stale ccb, second dptfreeccb inserting the dccb into the free list twice (two later callers share one dccb), second xpt_done of a possibly-recycled CAM ccb. Also reachable without a hostile device on SMP: dpttimeout calls dpt_intr racing the hardirq while both read the busaddr before the clear. Reject-inactive fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4241 Β· 15 files
FileTypeDescriptionSize
dfdpt_rig.c β€” 37.5 KB view raw
dfdpt_ctl.c β€” 7.5 KB view raw
Makefile β€” 108 B ↓ download
dpt.h β€” 38.3 KB view raw
build.sh β€” 92 B view raw
run.sh β€” 261 B view raw
build.log β€” 1.6 KB view raw
run.log β€” 3.1 KB view raw
run.2.log β€” 2.6 KB view raw
panic.txt β€” 1.8 KB view raw
fix_validation.log β€” 3.7 KB view raw
fix.diff β€” 877 B view raw
verdict.json β€” 4.8 KB view raw
VERDICT.md β€” 4.2 KB ↓ raw
env.txt β€” 699 B view raw
VERDICT.md
↓ download raw

DF-4241 β€” VERDICT

REPRODUCED (kernel list corruption + double xpt_done β†’ CAM completion panic; fix validated on a rebuilt kernel).

Chain, all production code

  1. rig registers a real cam_sim over a fabricated post-attach softc (real bus_dma; free list built by production dptallocccbs); the sim's handlers are the shipped kernel's dpt_action/dpt_poll.
  2. ./dfdpt_ctl queue β†’ xpt_action with a REAL xpt_alloc_ccb'd XPT_SCSI_IO β†’ production dpt_action (0xffffffff80438010) β†’ dptexecuteccb β†’ dccb X (0xfffff801185b78e8) is DCCB_ACTIVE, on pending_ccb_list, callout armed, ccb_h.status = CAM_SIM_QUEUED, ccb_dccb_ptr = X.
  3. ./dfdpt_ctl intr 0 1 0 0 (legit single status packet, hba_stat=0/scsi_stat=0/EOC=1, DMA'd into the sp by the "device"): production dpt_intr (0xffffffff80439670) maps the busaddr to X (only range-checked, dpt_scsi.c:1518-1527), completes it: CAM_REQ_CMP, rig_done fires (comp=1), dptfreeccb inserts X into the free list once. Everything clean.
  4. ./dfdpt_ctl dupalias 0 β€” the device delivers a duplicate of the same packet. Production dpt_intr again maps X β€” still no DCCB_ACTIVE check β€” and: - callout_stop on the stale (already completed, CAM-owned) ccb's timeout; - dptfreeccb(dpt, X) with state == DCCB_FREE: no LIST_REMOVE, but SLIST_INSERT_HEAD runs again β†’ self-loop (head->next == head, rig prints SELFLOOP=1, dups=200/200 = unbounded walk, free_dccbs inflated to 1); - xpt_done(ccb) a second time β†’ the completed ccb is re-enqueued on sim_doneq.
  5. Still inside the same ioctl (rig lock held, so the CAM SWI cannot race the capture), two more real XPT_SCSI_IOs are queued: production dptgetccb pops the self-looped head twice β†’ both CCBs get the SAME dccb (SHARED=1) β€” the shared-command-block UAF; the rig then releases the lock.
  6. The CAM software-interrupt handler processes the corrupt sim_doneq: panic: Bad link elm 0xfffff801185f0ec0 prev->next != elm in camisr_runqueue β€” and 0xfffff801185f0ec0 is exactly the CCB from step 1 (QUEUE id=0 ccb=...). System down in DDB. (First observed serendipitously in an earlier boot β€” run.2.log β€” then reproduced cleanly with the capture-under-lock sequence β€” run.log.)

Fix validation (fix.diff applied, kernel rebuilt + rebooted)

Same rig, same sequence (fix_validation.log): - the duplicate packet now hits the added guard: dpt9: status packet for inactive CCB ignored (production dpt_intr, dpt_scsi.c:1551ff in the patched file), IRQ cleared, loop continues β€” no double free, no double xpt_done; - free list healthy: SELFLOOP=0 dups=1 walk=16 ctr=0; - no aliasing: the two subsequent queues got different dccbs (0x...8e8 vs 0x...850, SHARED=0); - no panic: dupalias returned to userland (DUPALIAS_RC=0), guest stayed up; - the legitimate completion path is unaffected (comp=1, CAM_REQ_CMP β€” captured in the same log).

Threat model

sp->ccb_busaddr is DMA'd by the controller (dpt.h:523): a hostile device (or passed-through/virtualized HBA) sends a duplicate/fabricated completion for any in-range busaddr. The SMP variant in the finding (dpttimeout β†’ dpt_intr racing the hardirq, dpt_scsi.c:1696) is the same missing-in-flight-check root cause; the demonstrated primitive (device-fed double completion) is the superset.

Caveats

  • The rig calls dpt_intr from an ioctl thread (not a hardirq) β€” irrelevant to the bug: the corruption happens inside dpt_intr proper, and the detonation happens in the CAM SWI thread exactly as it would from a real interrupt.
  • The rig's duplicate is byte-identical to the legit packet, which is exactly the "duplicate completion" case; the fabricated-in-range-busaddr case (any other dccb) is the same code path with the same missing check.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Validated on the rebuilt kernel: duplicate packet rejected by the DCCB_ACTIVE guard ('status packet for inactive CCB ignored'), free list intact (no self-loop, counter correct), subsequent queues get distinct dccb's, no panic, and the normal completion path (comp=1 / CAM_REQ_CMP) unaffected.

fix_validation.log
↓ fix.diffper-fix-DF-4241

Confirmed kernel references

Detail

Exploit chain

duplicate/fabricated device status packet (in-range ccb_busaddr, no in-flight check) -> double dptfreeccb -> free-list self-loop -> shared dccb across two in-flight CCBs (UAF-family aliasing; each later completion frees/recycles the other's command block) -> double xpt_done on a CAM-owned ccb -> sim_doneq list corruption -> panic in camisr_runqueue (kernel down). On a hostile-device threat model the double xpt_done is a use-after-free of a recycled CAM ccb -- the demonstrated crash is the primitive detonating, not its ceiling.

Evidence (decisive lines)

run.log (serial console: QUEUE id=0 ccb=0xfffff801185f0ec0 ... DUP post SELFLOOP=1 dups=200 walk=200 ctr=1 ... ALIAS SHARED=1 ... panic: Bad link elm 0xfffff801185f0ec0 in camisr_runqueue+0x574 / swi_cambio); panic.txt (panic block); run.2.log (first, serendipitous reproduction in an earlier boot); fix_validation.log (inactive-CCB-ignored + SELFLOOP=0 + SHARED=0 + no panic)

PoC changes

No seed PoC existed. Built the production-handler dispatch rig (real cam_sim with production dpt_action/dpt_poll over a fabricated post-attach softc with real busdma and a production-dptallocccbs-built free list; real CCBs via xpt_action; status packets DMA-written by the emulated device). Added a dupalias ioctl that performs the duplicate completion, the free-list capture, and two follow-up queues under one lock hold so the evidence is captured before the CAM SWI detonates.

Verified recommended fix

Reject status packets whose dccb is not DCCB_ACTIVE (clear IRQ and continue), as in fix.diff.

Verdict

REPRODUCED with the full production path: a real xpt_alloc_ccb'd XPT_SCSI_IO queued through the production dpt_action/dptexecuteccb (shipped kernel image) leaves dccb X in flight; the device's first EATA status packet completes it cleanly (comp=1, CAM_REQ_CMP, freed once). A byte-identical DUPLICATE packet (device-fed sp->ccb_busaddr, only range-checked at dpt_scsi.c:1518-1527) makes production dpt_intr re-complete X: dptfreeccb inserts X into free_dccb_list a second time -> SELFLOOP=1 (head->next==head, unbounded walk, free_dccbs inflated +1); two subsequent production XPT_SCSI_IOs then BOTH pop the same dccb (SHARED=1 -- two live CCBs on one command block, the shared-object UAF); and the second xpt_done(ccb) re-enqueues the completed CAM ccb on sim_doneq, where camisr_runqueue panics: 'Bad link elm 0xfffff801185f0ec0 prev->next != elm' -- exactly the CCB queued at step 1 (QUEUE id=0 ccb=0xfffff801185f0ec0). System down in DDB. FIX VALIDATED on a rebuilt kernel: the added DCCB_ACTIVE guard prints 'dpt9: status packet for inactive CCB ignored', the free list stays healthy (SELFLOOP=0, dups=1), subsequent queues get DIFFERENT dccbs (SHARED=0), no panic (dupalias returns to userland), and the legitimate completion path still works.