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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3898 Β· 8 files
FileTypeDescriptionSize
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:

  • lksleep times out, detach proceeds, sassc->targets and sassc are freed (and cam_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), dereferences sassc->sc, sassc->targets[...], TAILQ_REMOVE on sassc->ccb_scanq (mps_sas.c:2906-2910), and on exit writes sassc->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).

VERDICT.md
↓ download raw

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

  1. sys/dev/raid/mps/mps_sas.c:820-828 β€” detach requests MPSSAS_SHUTDOWN and waits a maximum of 30 s (lksleep(..., 30 * hz)).
  2. sys/dev/raid/mps/mps_sas.c:2897-2917 β€” the scanner thread checks the flag only (a) after each 1 s lksleep and (b) after xpt_action(ccb) returns. xpt_action on an XPT_SCAN_BUS ccb executes synchronous CAM probes through mpssas_action for 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.
  3. sys/dev/raid/mps/mps_sas.c:829-833 β€” after the bounded sleep the function unconditionally executes kfree(sassc->targets) and kfree(sassc) (and cam_sim_free(sassc->sim) already happened at mps_sas.c:815-818) regardless of MPSSAS_SCANTHREAD still being set.
  4. 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 write sassc->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_testable
baseline no→ patch + rebuild →patched clean

fix.diff converts the overrun into a warned leak; cannot be exercised on this guest (no SAS2 HBA).

findings/poc/DF-3898/fix.diff
↓ fix.diffper-fix-DF-3898

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.