mfip_detach frees the CAM sim without draining mfi_cam_ccbq or clearing mfi_cam_start: queued passthrough ccbs leak permanently (unkillable hang) and are later dispatched through the stale callback into deregistered/freed CAM state
Summary
The SIM-private queue is invisible to xpt_bus_deregister (in-source XXX admits it) and there is no XPT_ABORT case - queued ccbs leak with the issuing periph blocked forever, and the still-live parent later pops them through the stale mfip_start (post-unload = call into unmapped module text; the late xpt_done completes against deregistered CAM state). DF-4438-style choreography mirror: 3 violations in stock mode, 0 fixed; drain+clear fix compiles into mfip.ko -Werror. MegaRAID hw-gated, root-triggered detach.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4971 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| mfip_detach_mirror.c | β | 10.7 KB | view raw | |
| build.sh | β | 109 B | view raw | |
| run.sh | β | 136 B | view raw | |
| build.log | β | 74 B | view raw | |
| run.log | β | 1.0 KB | view raw | |
| run.2.log | β | 652 B | view raw | |
| env.txt | β | 942 B | view raw | |
| fix.diff | β | 889 B | view raw | |
| fix_build.log | β | 1.2 KB | view raw | |
| manifest.json | β | 897 B | view raw | |
| verdict.json | β | 4.1 KB | view raw |
DF-4971 β mfip_detach leaks queued CAM ccbs and leaves a stale mfi_cam_start callback
sys/dev/raid/mfi/mfi_cam.c:142-159 β mfip_detach() deregisters the bus
and frees the sim but (a) never drains sc->mfi_sc->mfi_cam_ccbq β ccbs the
SIM accepted via mfip_cam_action XPT_SCSI_IO (mfi_cam.c:242) but the
controller had no free command for β and (b) never clears
sc->mfi_sc->mfi_cam_start (set at mfi_cam.c:116). xpt_bus_deregister
cannot recover SIM-private ccbs (cam_xpt.c:4383 XXX), and mfip_cam_action
has no XPT_ABORT support, so the leaked ccbs hang forever; the still-live
parent controller's mfi_startio (invoked from mfi_intr at mfi.c:1245 and
from mfi_shutdown, both running after child deletion in
mfi_pci_detach mfi_pci.c:328-335) then pops them through the stale
callback and completes them via xpt_done on a deregistered bus
(path->bus->sim == &cam_dead_sim, cam_xpt.c:4456) with periphs already
destroyed β a CAM-state UAF shape; on kldunload the callback target is
unmapped module text.
Files
mfip_detach_mirror.cβ choreography mirror: DFly CAM refcount semantics transcribed from cam_sim.c/cam_xpt.c + verbatim mfip_attach/action/start detach structure; stock vs fixed detachbuild.sh,run.sh,build.log,run.log,run.2.logfix.diffβ drain + clear-callback fixfix_build.logβ patchedmfip.kobuilds on the guest with-Werrorenv.txtβ guest environment (no LSI MegaRAID HBA)
Reproduce
sh build.sh && sh run.sh
Expected decisive lines (BUGGY mode):
post-detach: sim freed=1 bus sim=dead_sim queued-nonempty=YES mfi_cam_start=STALE (mfip_start)
V1 VIOLATED: 3 ccb(s) leaked in mfi_cam_ccbq ...
V2 VIOLATED: mfi_cam_start still set after sim freed
[xpt_done] UAF: ccb 1 completed after its periph was destroyed (dispatch sim=0x... dead_sim)
[mfip_start] UAF: call into unmapped module text after kldunload
BUGGY: 4 violation(s) exit=1
FIXED mode: FIXED: 0 violation(s) exit=0.
Kernel-path reproduction is impossible on this guest (no LSI 0x1000 PCI device; QEMU has no MegaRAID model) β see VERDICT.md.
DF-4971 VERDICT β mfip_detach lifecycle bug: static trace complete + choreography mirror; kernel path not instantiable on guest
Bottom line
mfip_detach() (mfi_cam.c:142-159) frees the sim without (a) draining the
SIM-private CAM queue mfisc->mfi_cam_ccbq and without (b) clearing
mfisc->mfi_cam_start. Both consequences are statically certain and are
demonstrated by a userspace mirror of the exact choreography run on the
DragonFly guest:
- V1 β ccb leak / unrecoverable hang. ccbs accepted by
mfip_cam_actionXPT_SCSI_IO (mfi_cam.c:242) park onmfi_cam_ccbqwhenever the controller has no free command (mfip_startreturns NULL at mfi_cam.c:268-269 without dequeuing).xpt_bus_deregisterwalks the devq/device ccbqs (cam_xpt.c:4402-4421) β it never sees SIM-private entries; the XXX at cam_xpt.c:4383 states AC_LOST_DEVICE "does not precisely abort the pending requests". Andmfip_cam_actionhas no XPT_ABORT case (default β CAM_REQ_INVALID, mfi_cam.c:246-248), so neither CAM nor the SIM ever completes the leaked ccb: the issuing peripheral (probe/pass/ses) blocks forever, the ccb memory leaks, andmfip_detach/kldunloadcannot be completed cleanly. - V2 β stale callback.
mfi_pci_detachdeletes children first (mfi_pci.c:328) and only afterwards quiesces the controller (EVENTHANDLER_DEREGISTER/mfi_shutdown/mfi_free at :332-335). In that window every completion callsmfi_startio(mfi.c:1245), which popsmfi_cam_ccbqthroughmfisc->mfi_cam_start(mfi.c:2132-2133) β stillmfip_start, whose sim was just freed. Onkldunloadof mfip/mfi the callback target is unmapped linker text (call to freed kernel text). - V3 β completion through deregistered CAM state. The late
mfip_donerunsxpt_done(ccb)(mfi_cam.c:378) after the bus was deregistered:xpt_donedispatches viaccb->ccb_h.path->bus->sim(cam_xpt.c:4898), retargeted tocam_dead_simat cam_xpt.c:4456, and readspath->periph->typeβ periphs of "aborted" ccbs were destroyed by the deregistration asyncs; a ccb CAM believed aborted (but which was actually leaked in our queue) completes against freed periph state β a use-after-free-shaped CAM state violation.
Why the mirror is faithful
cam_sim_alloctakes its own devq ref (cam_sim.c:185) and never fails (M_INTWAIT);cam_simq_releaseafter alloc is correct β modeled.xpt_bus_registerbumps sim refcount (cam_xpt.c:4322); deregister retargets bus->sim to dead_sim (4456) and drops the ref (4484);cam_sim_free(cam_sim.c:229-234) frees at refcount 1 β modeled.mfip_attach/mfip_cam_action/mfip_start/mfi_startiostructures are the verbatim stock code paths (pointers/locks simplified, queue and callback semantics exact).
Result (guest, 2 runs + fixed variant)
BUGGY: 4 violations (V1 leak count 3, V2 stale cb, V3 xpt_done UAF line,
unmapped-text call). FIXED (drain + mfi_cam_start = NULL): 0 violations.
fix.diff additionally compiles into mfip.ko on the guest with -Werror
(fix_build.log).
Why not a kernel-path reproduction
Requires an LSI MegaRAID SAS controller: mfip only exists as a child of
mfi (mfi.c:737), which only probes on LSI PCI IDs. The guest has zero
0x1000-vendor PCI devices and QEMU has no MegaRAID device model (env.txt).
Status inconclusive per the family precedent (DF-4438, same-shape
mrsas_cam finding).
Impact ceiling (honest)
On real hardware: permanent uninterruptible hang of any thread waiting on a passthrough ccb + kernel ccb leak at every detach with queued ccbs (boot-scan INQUIRYs and camcontrol traffic both queue here), and post-detach dispatch of stale ccbs through freed sim/unmapped module text / deregistered periph state β a UAF-shaped escalation surface, though no controlled write was demonstrated. Trigger requires MegaRAID hardware + pending mfip-bus ccb at detach/unload (root-initiated action, or surprise removal / shutdown race).
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
inconclusiveKernel-level fix validation impossible (no MegaRAID HBA on guest). Validated at two levels: (1) the mirror's fixed mfip_detach drains the queue and clears the callback - 0 violations where stock shows 4 (run.log); (2) fix.diff applies cleanly to the guest /usr/src copy and mfip.ko builds with -Werror (fix_build.log). Guest sources restored after the build.
run.log FIXED section ('FIXED: 0 violation(s) exit=0'); fix_build.log ('Hunk #1/#2 succeeded', cc -Werror, ld -r mfip.ko, RESTORED_CLEAN)
Confirmed kernel references
- sys/dev/raid/mfi/mfi_cam.c:142
- sys/dev/raid/mfi/mfi_cam.c:116
- sys/dev/raid/mfi/mfi_cam.c:242
- sys/dev/raid/mfi/mfi_cam.c:246
- sys/dev/raid/mfi/mfi_cam.c:268
- sys/dev/raid/mfi/mfi_cam.c:378
- sys/dev/raid/mfi/mfi.c:1245
- sys/dev/raid/mfi/mfi.c:2132
- sys/dev/raid/mfi/mfi_pci.c:328
- sys/bus/cam/cam_xpt.c:4383
- sys/bus/cam/cam_xpt.c:4456
Detail
Evidence (decisive lines)
run.log: 'post-detach: ... queued-nonempty=YES mfi_cam_start=STALE', 'V1 VIOLATED: 3 ccb(s) leaked', 'V2 VIOLATED', '[xpt_done] UAF: ccb 1 completed after its periph was destroyed (dispatch sim=... dead_sim)', '[mfip_start] UAF: call into unmapped module text after kldunload', 'BUGGY: 4 violation(s) exit=1' vs 'FIXED: 0 violation(s) exit=0'; fix_build.log shows patched mfip.ko linking with -Werror and source restore
PoC changes
mirror authored fresh (no seed); DragonFly userland queue.h needs plain TAILQ field names (kernel camq_entry union uses sim_links.tqe); post-detach free-command replenishment added to model mfi_intr completing a disk bio before mfi_startio
Verified recommended fix
In mfip_detach, under mfi_io_lock: drain mfi_cam_ccbq completing each ccb with CAM_REQ_ABORTED via xpt_done, set sc->mfi_sc->mfi_cam_start = NULL, then xpt_bus_deregister + cam_sim_free
Verdict
Static trace complete; kernel path not instantiable on the audit guest (no LSI 0x1000 PCI device, QEMU has no MegaRAID model; mfip exists only as a child of mfi, mfi.c:737). mfip_detach (mfi_cam.c:142-159) frees the sim without draining mfisc->mfi_cam_ccbq and without clearing mfisc->mfi_cam_start (set at mfi_cam.c:116). Queued ccbs (parked whenever the controller had no free command, mfi_cam.c:268) are invisible to xpt_bus_deregister (cam_xpt.c:4383 XXX) and unrecoverable through the sim (no XPT_ABORT case, mfi_cam.c:246 default CAM_REQ_INVALID): permanent hang + ccb leak. The still-live parent then dispatches them through the stale callback: mfi_pci_detach deletes children first (mfi_pci.c:328) and mfi_intr->mfi_startio (mfi.c:1245->2132-2133) runs before mfi_free, popping the leaked ccbs via mfip_start whose sim is freed; on kldunload the callback is unmapped module text; the late xpt_done (mfi_cam.c:378) completes the ccb against deregistered CAM state (path->bus->sim==&cam_dead_sim per cam_xpt.c:4456) with periphs destroyed - UAF-shaped. Guest-compiled choreography mirror (CAM refcount semantics transcribed from cam_sim.c/cam_xpt.c) shows all violations in stock mode and zero with the drain+clear fix; fix.diff builds into mfip.ko on the guest with -Werror.
No comments yet.