mps_user_pass_thru frees the in-flight command and its DMA buffer the instant mps_wait_command() times out (use-after-free with device DMA writeback)
Summary
mps_wait_command returns ETIMEDOUT without aborting the command - the chip still owns the request/reply frames and the SGL over cm->cm_data. mps_user_pass_thru immediately kfrees cm->cm_data and returns the slot to req_list: the SMID stays outstanding while both the slot and DMA target are reusable - late completion mutates a possibly-reused command and residual device DMA lands in freed memory. TM branch same defect. Opposite (more dangerous) direction than the DF-4010 mpr twin. Abandon-policy fix compiled+booted; live before/after needs the HBA.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4014 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 451 B | β raw | |
| VERDICT.md | file | 4.3 KB | β raw | |
| fix.diff | file | 1.3 KB | view raw | |
| fix_kernel_build.log | file | 767 B | view raw | |
| verdict.json | file | 3.7 KB | view raw |
DF-4014 β mps_user_pass_thru frees the in-flight command on wait timeout (UAF)
Source-certain (no LSI HBA on the audit guest, so no live run). See
VERDICT.md for the line-complete trace: mps.c:2118-2132 (no abort on
ETIMEDOUT) -> mps_user.c:977-982 -> :1039-1046 free-while-in-flight;
mpsvar.h:514-540 shows the slot returning to the free list. fix.diff
abandons the command instead (the driver's own config-page policy,
mps_user.c:240-245).
DF-4014 β VERDICT
Verdict
Source-certain, HW-gated (not live-reproducible on this guest: no LSI SAS
HBA, /dev/mps* absent). mps_user_pass_thru() frees its command β and the
user-sized cm->cm_data buffer the chip's SGL still points at β the moment
mps_wait_command() returns ETIMEDOUT. mps_wait_command()
(sys/dev/raid/mps/mps.c:2118-2132) performs no abort: it lksleep()s on
the command and returns ETIMEDOUT while the request is still enqueued to
the hardware with MPS_CM_FLAGS_WAKEUP set. The immediate free hands the
command slot back to the allocation free list and kfree()s the DMA target
while the chip retains the SMID and the SGL β a classic free-while-in-flight
(use-after-free) with device-driven writeback into freed memory.
Bug class
Use-after-free / DMA-after-free triggered by command timeout. The mpr
sibling driver has the opposite defect (DF-4010: it leaks the buffer
across its reinit path because mpr_wait_command() takes &cm and the
reinit frees the command itself) β DragonFly's mps kept the old
single-pointer wait API and the immediate free, which is the memory-unsafe
direction.
Source trace (path:line)
sys/dev/raid/mps/mps_user.c:975βerr = mps_wait_command(sc, cm, 30);undermps_lock.sys/dev/raid/mps/mps.c:2126-2130β onlksleep()timeout the function returnsETIMEDOUT; nothing dequeues or abortscm(the chip still owns the request frame, reply frame and the SGL built overcm->cm_data).sys/dev/raid/mps/mps_user.c:977-982βif (err) { ...; mps_unlock(sc); goto RetFreeUnlocked; }.sys/dev/raid/mps/mps_user.c:1039-1046βRetFreeUnlocked:takes the lock,kfree(cm->cm_data, M_MPSUSER)andmps_free_command(sc, cm).sys/dev/raid/mps/mpsvar.h:514-540βmps_free_command()resets the command toMPS_CM_STATE_FREEandTAILQ_INSERT_TAIL(&sc->req_list, ...); the slot is instantly allocatable by any other thread (CAM I/O, another ioctl) while the outstanding SMID still refers to it.- Late completion: the reply descriptor's SMID resolves back to this
cm;cm_replyis written by hardware/DMA and the completion path mutates a command that may already be reused (cross-command corruption), and the chip's residual DMA lands in the freedcm->cm_datachunk.
The task-management branch has the same defect:
sys/dev/raid/mps/mps_user.c:838-844 (wait, EIO) β :863 mpssas_free_tm()
frees the in-flight TM command.
Contrast with the driver's own policy for config pages
(sys/dev/raid/mps/mps_user.c:240-245): "Leave the request. Without
resetting the chip, it's still owned by it and we'll just get into trouble
freeing it now." β the pass-thru paths violate exactly this.
Reachability
Any operator-group user (0640 /dev/mpsN, ioctls not gated on open mode β
see DF-1359/DF-1326 boundary analysis) can issue MPTIOCTL_PASS_THRU with a
request the firmware takes β₯30 s on (or never completes); the timeout then
arms the UAF. Exploitability on real hardware: medium-high (requires an HBA
and control over command latency, but the free-then-DMA window is wide and
repeatable).
Why not live-reproduced
No LSI Fusion-MPT PCI device exists on the audit QEMU guest; the shipped
driver never attaches (/dev/mps* absent), so the wait/timeout path cannot
be entered. A KLD replica of a chip-async timeout was judged out of scope
for fidelity: the essence of the bug is the device's late DMA into freed
memory, which a stubbed replica can only assert structurally, not
demonstrate. The source trace above is line-complete.
Fix
fix.diff β abandon the command on wait-timeout (both the TM branch and the
normal branch route to Ret without freeing), matching the driver's own
documented abandon policy for timed-out config requests. The abandoned
command is reclaimed on the next diag reset. Note: this trades the UAF for a
bounded leak until a proper mps_abort_command() exists; the companion
mps_user_command() path (:707-711) already leaks by design (DF-4015).
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 dry-run applies cleanly; combined nativekernel build (DF-4012+4013+4014 diffs together) rc=0 under -Werror, installed and booted (kern.version 'DragonFly 6.5-DEVELOPMENT #1: Thu Sep 17 14:24:13 UTC 2026'). Live before/after comparison is impossible on this guest (no mps HBA to drive the timeout path), hence not_testable rather than fixed.
fix.diff; fix_kernel_build.log
Confirmed kernel references
Detail
Exploit chain
operator-group user -> MPTIOCTL_PASS_THRU with a request the firmware holds >30 s -> ETIMEDOUT -> immediate kfree(cm->cm_data) + mps_free_command(cm) while the chip still DMAs per the SGL and the SMID stays outstanding -> freed chunk reused by slab grooming -> device DMA / late-completion writes corrupt the replacement object -> on real hardware: kernel heap corruption toward uid=0 (characterization needs the HBA).
Evidence (decisive lines)
VERDICT.md (line-complete trace), fix.diff (abandon-on-timeout routing both branches to Ret, mirroring the driver's own config-page policy)
PoC changes
no kernel replica built: the essence of the bug is the device's late DMA into freed memory, which a stubbed replica cannot demonstrate beyond the source trace
Verified recommended fix
On wait-timeout, abandon the command instead of freeing it (fix.diff), matching the driver's documented config-page policy; long term add a real mps abort/reinit path.
Verdict
Source-certain use-after-free, not live-reproducible on this guest (no LSI Fusion-MPT HBA, /dev/mps* absent, so the wait/timeout path cannot be entered). mps_wait_command() (mps.c:2118-2132) returns ETIMEDOUT WITHOUT aborting the command -- the chip still owns the request/reply frames and the SGL over cm->cm_data. mps_user_pass_thru() then immediately kfrees cm->cm_data and mps_free_command()'s the slot back to sc->req_list (mps_user.c:977-982 -> :1039-1046; mpsvar.h:514-540), handing both to new users while the outstanding SMID still refers to them: a late completion mutates a possibly-reused command (cross-command corruption) and the chip's residual DMA lands in freed memory. The task-management branch has the same defect (:838-844 -> mpssas_free_tm at :863). This is the DF-4010 (mpr) site with the opposite and more dangerous defect: mpr LEAKS the buffer across its reinit; mps FREES while in flight. The driver itself documents the correct policy for config pages (mps_user.c:240-245: 'Leave the request... we'll just get into trouble freeing it now').
No comments yet.