mvfrey CCB-timeout callout runs iop_intr without the adapter lock, racing the locked hard-IRQ drain: double srb completion (double free to freelist + double xpt_done β CAM UAF); plus 60s DELAY busy-loop in softclock and the timed-out CCB is never completed
Summary
Every mvfrey SCSI post arms a 20s per-CCB callout. The timeout path calls iop_intr with NO lock, while the hard IRQ runs the same iop_intr holding the lock. The mvfrey drain uses shared non-atomic outlist_rptr: concurrent execution completes the same tag twice -> free_srb twice (freelist aliasing: two CCBs share one srb) and xpt_done(ccb) twice (CAM UAF). Additionally the reset busy-waits up to 60s in softclock and the timed-out CCB is never completed. Siblings hptmv/hpt27xx lock in their timeout handlers - hptiop is the outlier. Any stalled mvfrey command (RR43xx/45xx wedge) whose 20s timeout fires while later completions still arrive. Lock-the-reset fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3962 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.8 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| trigger.c | β | 2.5 KB | view raw | |
| build.sh | β | 67 B | view raw | |
| run.sh | β | 91 B | view raw | |
| run.log | β | 12 B | view raw | |
| env.txt | β | 1021 B | view raw | |
| fix.diff | β | 945 B | view raw | |
| build.log | β | 7.1 KB | view raw | |
| manifest.json | β | 370 B | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-3962 β hptiop mvfrey CCB-timeout callout runs iop_intr unlocked, racing the locked IRQ path (double SRB completion β UAF)
File: sys/dev/raid/hptiop/hptiop.c (pass 2; knowns DF-1311/1312/1313 excluded)
What
Every mvfrey SCSI command arms a 20 s per-CCB callout
(callout_reset(ccb->ccb_h.timeout_ch, 20*hz, hptiop_reset_adapter, hba)
hptiop.c:2647β2650). The handler hptiop_reset_adapter (2244β2250) calls
hptiop_send_sync_msg (891β907) which polls hba->ops->iop_intr(hba)
without hba->lock. The hard-IRQ path hptiop_pci_intr (2163β2169) runs
the identical iop_intr with the lock. Two CPUs can therefore drain the
mvfrey outlist concurrently through the non-atomic outlist_rptr loop
(782β795): both process the same tag β hptiop_free_srb() twice (srb on the
freelist twice β two CCBs share one srb) and xpt_done(ccb) twice (CAM UAF).
Additionally: the timed-out CCB is never completed by its own timeout handler
(callout arg is hba), and the reset path busy-waits up to 60 s (DELAY
loop in softclock context).
Sibling HighPoint drivers lock in their timeout handlers
(hptmv/entry.c:2626 lock_driver(); fResetVBus(), hpt27xx_osm_bsd.c:530) β
hptiop is the outlier.
Build / Run (hardware recipe)
Needs an RR43xx/45xx (mvfrey family) present and a stalled command: kldload hptiop; saturate I/O; wedge/expel the device past 20 s while later completions still arrive; observe double-completion panic (xpt_done on a done ccb) or freelist aliasing corruption.
Expected
CAM assertion/panic xpt_done twice, or silent srb aliasing (two commands
writing one request buffer) β memory corruption.
Honesty
Hardware-gated (no mvfrey HBA on guest). The race is certain from source: no lock, no atomics, shared cursors. The 60 s DELAY in a callout is independently a DoS.
DF-3962 verdict β certain source-level race, hardware-gated
Reproduced?
No: guest has no HighPoint/mvfrey device (env.txt). Source analysis is conclusive.
The race, path:line
- hptiop.c:2647β2650 β per-CCB 20 s callout armed on every mvfrey SCSI post;
arg is
hba(not the ccb), so the timeout never completes its CCB. - hptiop.c:2244β2250 β
hptiop_reset_adaptercallshptiop_send_sync_msg(hba, IOPMU_INBOUND_MSG0_RESET, 60000)with NO adapter lock. XPT_RESET_BUS calls it under the lock (2350β2353) β the lock isLK_CANRECURSE(1899) so fix.diff locking is safe for both paths. - hptiop.c:899β906 β
hptiop_send_sync_msgpollshba->ops->iop_intr(hba)directly; for mvfrey that ishptiop_intr_mvfrey(760β802) which drains the outlist with the shared, non-atomichba->u.mvfrey.outlist_rptr(782β795) and completes tags viahptiop_request_callback_mvfrey. - hptiop.c:2163β2169 β
hptiop_pci_intrholds the lock around the sameiop_intr; lock discipline is inconsistent between the two callers, so there is NO mutual exclusion between them. - Both CPUs can read the same outlist entry β same
_tagcompleted twice βhptiop_free_srbtwice (srb freelist aliasing: two future CCBs share one srb and its DMA request buffer) andxpt_done(ccb)twice (CAM UAF). - Bonus: 60000 ms of
DELAY(1000)polling inside a callout (softclock context) starves the CPU; and reset wipes firmware queues while in-flight SRBs are never reaped (ccb leak / CAM wedge).
Sibling comparison: sys/dev/raid/hptmv/entry.c:2626-2633 and sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c:528-532 both take their driver lock inside the timeout handler before resetting.
Fix
fix.diff takes hptiop_lock_adapter around the reset in
hptiop_reset_adapter (safe for the recursive XPT_RESET_BUS path).
A fuller upstream fix should also abort/complete the timed-out CCB and avoid
the 60 s busy-wait in callout context.
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 serializes the callout-driven iop_intr with the hard-IRQ path by acquiring the recursive adapter lock in hptiop_reset_adapter; git apply --check passes against pristine sys/. Runtime validation impossible without the HBA fix.diff applied to the guest /usr/src copy and the hptiop.ko module rebuilt clean with -Werror (build.log; combined with the other four hptiop fix.diffs of this batch); runtime validation still impossible without the HBA.
fix.diff (git apply --check clean); fix.diff applied to the guest /usr/src copy and the hptiop.ko module rebuilt clean with -Werror (build.log; combined with the other four hptiop fix.diffs of this batch); runtime validation still impossible without the HBA.
Confirmed kernel references
- sys/dev/raid/hptiop/hptiop.c:2647
- sys/dev/raid/hptiop/hptiop.c:2244
- sys/dev/raid/hptiop/hptiop.c:891
- sys/dev/raid/hptiop/hptiop.c:899
- sys/dev/raid/hptiop/hptiop.c:2163
- sys/dev/raid/hptiop/hptiop.c:782
- sys/dev/raid/hptiop/hptiop.c:634
- sys/dev/raid/hptiop/hptiop.c:639
- sys/dev/raid/hptmv/entry.c:2626
- sys/dev/raid/hpt27xx/hpt27xx_osm_bsd.c:530
Detail
Exploit chain
mvfrey command stalls >20s while later completions arrive -> callout fires hptiop_reset_adapter on CPU0 (unlocked) as a completion IRQ arrives on CPU1 (locked) -> both drain the same outlist entry -> double srb completion -> srb appears twice on the free list -> next two posts alias one request buffer (16-byte-stride descriptor corruption between commands) and double xpt_done panics or corrupts CAM state; independently the 60s DELAY loop in softclock stalls the CPU (DoS).
Evidence (decisive lines)
VERDICT.md (path:line race chain + sibling comparison), trigger.c (source excerpt + hardware recipe), env.txt, fix.diff (lock acquisition, git apply --check clean)
PoC changes
No runnable PoC: needs an mvfrey-family HBA (RR43xx/45xx) with a stalled command and concurrent completion; trigger.c carries the annotated source chain and hardware recipe.
Verified recommended fix
Take hptiop_lock_adapter in hptiop_reset_adapter before send_sync_msg (fix.diff); upstream should additionally complete/abort the timed-out CCB and drop the 60s callout-context busy-wait.
Verdict
Certain source-level race: hptiop_post_req_mvfrey arms a 20s per-CCB callout (hptiop.c:2647-2650) whose handler hptiop_reset_adapter (2244-2250) polls iop_intr via hptiop_send_sync_msg (899-906) WITHOUT hba->lock, while hptiop_pci_intr (2163-2169) runs the same iop_intr WITH the lock; the mvfrey outlist drain (782-795) uses the shared non-atomic outlist_rptr, so concurrent execution completes the same tag twice -> hptiop_free_srb twice (freelist aliasing, two CCBs share one srb) and xpt_done(ccb) twice (CAM UAF). Also: the timed-out CCB is never completed (callout arg is hba), and the reset busy-waits up to 60s in softclock. Siblings hptmv/hpt27xx lock in their timeout handlers. Not reproduced: no mvfrey HBA on the audit guest (env.txt).
No comments yet.