mps_detach_sas frees sassc/sim after a 30-second bounded wait even if the scanner thread never exited: use-after-free reads and writes from mpssas_scanner_thread
Summary
mps_detach_sas waits at most 30 s for the rescan thread then unconditionally frees sassc->targets/sassc - after cam_sim_free already ran - while the scanner thread only observes SHUTDOWN after its current xpt_action returns, and one XPT_SCAN_BUS leg over wedged targets (60 s CCB timeouts x retries) can far exceed 30 s. The live thread then uses the freed sim/sassc and performs freed-memory writes on exit. Privileged detach racing a stuck bus scan on failing targets - the exact scenario detach is used to recover from. Warn-and-leak fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3898 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 2.3 KB | β raw | |
| run.sh | β | 111 B | view raw | |
| run.log | β | 482 B | view raw | |
| env.txt | β | 274 B | view raw | |
| fix.diff | β | 901 B | view raw | |
| verdict.json | β | 2.4 KB | view raw | |
| manifest.json | β | 692 B | view raw |
DF-3898 β mps_detach_sas frees sassc after a 30-second bounded wait even if the scanner thread never exited: use-after-free
File: sys/dev/raid/mps/mps_sas.c (pass 2).
What
mps_detach_sas (sys/dev/raid/mps/mps_sas.c:783-836) shuts down the
rescan thread with a bounded wait and then frees the softc
unconditionally:
if (sassc->flags & MPSSAS_SCANTHREAD) {
sassc->flags |= MPSSAS_SHUTDOWN;
wakeup(&sassc->ccb_scanq);
if (sassc->flags & MPSSAS_SCANTHREAD) {
lksleep(&sassc->flags, &sc->mps_lock, 0,
"mps_shutdown", 30 * hz); /* mps_sas.c:825-826 β BOUNDED */
}
}
mps_unlock(sc);
kfree(sassc->targets, M_MPT2); /* mps_sas.c:831 */
kfree(sassc, M_MPT2); /* mps_sas.c:832 β even if thread alive */
The scanner thread (mpssas_scanner_thread, mps_sas.c:2885-2924) exits
only when it observes MPSSAS_SHUTDOWN β checked after its 1-second sleep
and after each xpt_action(ccb) (mps_sas.c:2900-2915). An XPT_SCAN_BUS
dispatched to this sim performs synchronous CAM probes of every
target/LUN; with devices in error recovery (60 s timeouts, 4 retries per
LUN β cf. mpssas_check_eedp's own 60 s/4-retry CCBs at mps_sas.c:3128-3130)
one scan leg can far exceed 30 s. When it does:
lksleeptimes out, detach proceeds,sassc->targetsandsasscare freed (andcam_sim_free(sassc->sim)already ran at mps_sas.c:817);- the still-running thread then executes
xpt_actionβmpssas_action(sim, ccb)βsassc = cam_sim_softc(sim)(freed sim), dereferencessassc->sc,sassc->targets[...], TAILQ_REMOVE onsassc->ccb_scanq(mps_sas.c:2906-2910), and on exit writessassc->flags &= ~MPSSAS_SCANTHREAD+wakeup(&sassc->flags)(mps_sas.c:2919-2920) β use-after-free reads and writes on kmalloc'd memory.
Trigger path: device detach / kldunload/driver reprobe while the bus
scan is stuck on wedged targets β i.e. exactly the failing-enclosure
scenario detach is used to recover from. Requires privilege (root performs
detach), hence Low.
Build/run
run.sh records the hardware gate.
Fix
If MPSSAS_SCANTHREAD is still set after the bounded sleep, do not free β leak the softc (and warn) instead of converting a stuck scan into a UAF (fix.diff).
DF-3898 β VERDICT
Status: not_reproduced (hardware-gated) β defect certain from source; trigger requires privileged detach + a >30 s scan leg. Impact class: use-after-free (kernel memory corruption) on kldunload β Low (privileged trigger).
Proof chain
sys/dev/raid/mps/mps_sas.c:820-828β detach requests MPSSAS_SHUTDOWN and waits a maximum of 30 s (lksleep(..., 30 * hz)).sys/dev/raid/mps/mps_sas.c:2897-2917β the scanner thread checks the flag only (a) after each 1 slksleepand (b) afterxpt_action(ccb)returns.xpt_actionon an XPT_SCAN_BUS ccb executes synchronous CAM probes throughmpssas_actionfor every target/LUN; device-side error recovery uses 60 s CCB timeouts with retries (the driver's own internal EEDP probe CCBs are 60 s Γ 4 retries, mps_sas.c:3128-3130), so a scan over wedged targets can run minutes.sys/dev/raid/mps/mps_sas.c:829-833β after the bounded sleep the function unconditionally executeskfree(sassc->targets)andkfree(sassc)(andcam_sim_free(sassc->sim)already happened at mps_sas.c:815-818) regardless of MPSSAS_SCANTHREAD still being set.- UAF sites in the still-alive thread:
cam_sim_softc(sim)use (mps_sas.c:897 via mpssas_action),TAILQ_REMOVE(&sassc->ccb_scanq, ...),xpt_action(mps_sas.c:2906-2911), and the exit path's writesassc->flags &= ~MPSSAS_SCANTHREAD; wakeup(&sassc->flags)(mps_sas.c:2919-2920) β freed-memory writes, i.e. genuine UAF, not just a hang.
Why not reproduced
Guest has no mps HBA (env.txt/run.log), so neither the thread nor a scan exists; additionally the trigger needs a privileged detach. Source sequence is complete and unconditional once the 30 s bound is exceeded.
Fix validation
Not testable here. fix.diff converts the overrun case into a warned leak (strictly better than UAF); the fully-correct upstream fix would make the thread exit wait unbounded or cancel the in-flight scan.
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 converts the overrun into a warned leak; cannot be exercised on this guest (no SAS2 HBA).
findings/poc/DF-3898/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (path:line proof of bounded wait vs unbounded scan leg), env.txt, run.sh + run.log (hardware gate), fix.diff (leak-not-UAF guard)
PoC changes
n/a β privileged detach + hardware-gated.
Verified recommended fix
If MPSSAS_SCANTHREAD is still set after the bounded sleep, warn and leak sassc instead of freeing it under the live thread; longer term make the thread wait unbounded.
Verdict
mps_detach_sas waits at most 30 seconds for the rescan thread (lksleep ... 30*hz, mps_sas.c:825-826) and then unconditionally frees sassc->targets and sassc (mps_sas.c:831-832) β after cam_sim_free already ran (mps_sas.c:815-818) β even though mpssas_scanner_thread only observes MPSSAS_SHUTDOWN after its current xpt_action returns, and a single XPT_SCAN_BUS leg over wedged targets (60 s CCB timeouts with retries, cf. mps_sas.c:3128-3130) can far exceed 30 s. The still-running thread then uses the freed sim/sassc (mpssas_action via cam_sim_softc, TAILQ_REMOVE on ccb_scanq at mps_sas.c:2906-2911) and performs freed-memory writes on exit (sassc->flags &= ~MPSSAS_SCANTHREAD at mps_sas.c:2919) β use-after-free. Trigger needs a privileged detach/kldunload racing a stuck bus scan on real hardware. Not reproduced: no SAS2 HBA in the guest; lifetime sequence certain from source.
No comments yet.