# 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:

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