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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3962 Β· 11 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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

  1. 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.
  2. hptiop.c:2244–2250 β€” hptiop_reset_adapter calls hptiop_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 is LK_CANRECURSE (1899) so fix.diff locking is safe for both paths.
  3. hptiop.c:899–906 β€” hptiop_send_sync_msg polls hba->ops->iop_intr(hba) directly; for mvfrey that is hptiop_intr_mvfrey (760–802) which drains the outlist with the shared, non-atomic hba->u.mvfrey.outlist_rptr (782–795) and completes tags via hptiop_request_callback_mvfrey.
  4. hptiop.c:2163–2169 β€” hptiop_pci_intr holds the lock around the same iop_intr; lock discipline is inconsistent between the two callers, so there is NO mutual exclusion between them.
  5. Both CPUs can read the same outlist entry β†’ same _tag completed twice β†’ hptiop_free_srb twice (srb freelist aliasing: two future CCBs share one srb and its DMA request buffer) and xpt_done(ccb) twice (CAM UAF).
  6. 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_testable
baseline no→ patch + rebuild →patched clean

fix.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.
↓ fix.diffper-fix-DF-3962

Confirmed kernel references

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).