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)
PoC verification
Evidence pack
findings/poc/DF-4241 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
DF-4241 β VERDICT
REPRODUCED (kernel list corruption + double xpt_done β CAM completion panic; fix
validated on a rebuilt kernel).
Chain, all production code
- 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'sdpt_action/dpt_poll. ./dfdpt_ctl queueβxpt_actionwith a REALxpt_alloc_ccb'd XPT_SCSI_IO β productiondpt_action(0xffffffff80438010) βdptexecuteccbβ dccbX(0xfffff801185b78e8) isDCCB_ACTIVE, onpending_ccb_list, callout armed,ccb_h.status = CAM_SIM_QUEUED,ccb_dccb_ptr = X../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"): productiondpt_intr(0xffffffff80439670) maps the busaddr to X (only range-checked, dpt_scsi.c:1518-1527), completes it:CAM_REQ_CMP,rig_donefires (comp=1),dptfreeccbinserts X into the free list once. Everything clean../dfdpt_ctl dupalias 0β the device delivers a duplicate of the same packet. Productiondpt_intragain maps X β still noDCCB_ACTIVEcheck β and: -callout_stopon the stale (already completed, CAM-owned) ccb's timeout; -dptfreeccb(dpt, X)withstate == DCCB_FREE: no LIST_REMOVE, butSLIST_INSERT_HEADruns again β self-loop (head->next == head, rig printsSELFLOOP=1,dups=200/200= unbounded walk,free_dccbsinflated to 1); -xpt_done(ccb)a second time β the completed ccb is re-enqueued onsim_doneq.- 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
dptgetccbpops the self-looped head twice β both CCBs get the SAME dccb (SHARED=1) β the shared-command-block UAF; the rig then releases the lock. - The CAM software-interrupt handler processes the corrupt
sim_doneq:panic: Bad link elm 0xfffff801185f0ec0 prev->next != elmincamisr_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_intrfrom an ioctl thread (not a hardirq) β irrelevant to the bug: the corruption happens insidedpt_intrproper, 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
fixedValidated 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
Confirmed kernel references
- sys/dev/raid/dpt/dpt_scsi.c:1518
- sys/dev/raid/dpt/dpt_scsi.c:1525
- sys/dev/raid/dpt/dpt_scsi.c:1527
- sys/dev/raid/dpt/dpt_scsi.c:1571
- sys/dev/raid/dpt/dpt_scsi.c:1604
- sys/dev/raid/dpt/dpt_scsi.c:1605
- sys/dev/raid/dpt/dpt_scsi.c:238
- sys/dev/raid/dpt/dpt_scsi.c:252
- sys/dev/raid/dpt/dpt_scsi.c:1696
- sys/dev/raid/dpt/dpt.h:523
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.
No comments yet.