# DF-3813 — VERDICT

**status: inconclusive (runtime)** — the code defect is source-certain; the
audit QEMU guest has no SAS3 controller, so the use-after-free cannot be fired
there. Source-level chain verified line-by-line below.

## Reproduced?

No. `poc_concurrent_reinit.c` compiles on the guest (`cc -O -Wall … -lpthread`,
BUILD_OK) and runs, exiting:

```
DF-3813: open /dev/mpr0: No such file or directory — no SAS3 controller present, cannot exercise mpr_wait_command()
```

`kldstat -v` confirms the driver itself is present (`142 pci/mpr`); only the
PCI device (and therefore `/dev/mpr0`) is absent. Neither proves nor disproves
the defect at runtime → **inconclusive**, per the no-hardware honesty rule.

## Why the defect is real (source trace)

1. `mpr_wait_command()` mpr.c:3577-3586 — sleeper path is
   `lksleep(cm, &sc->mpr_lock, 0, "mprwait", timeout*hz)`; the lock is
   released while asleep, so a concurrent reinit holder runs to completion
   before the sleeper reacquires.
2. Concurrent reinit (`mpr_reinit()` mpr.c:815, mpr_lock held):
   - mpr.c:853 `mprsas_handle_reinit()` → `mprsas_complete_all_commands()`
     (mpr_sas.c:1155-1210): for a waiter command (cm_complete == NULL,
     MPR_CM_FLAGS_WAKEUP set at mpr.c:3568) it takes the
     `else if (cm->cm_flags & MPR_CM_FLAGS_WAKEUP) { … wakeup(cm); }` branch —
     the command is NOT freed and stays owned by the sleeper.
   - mpr.c:861 `mpr_iocfacts_allocate(sc, FALSE)`; facts-changed test at
     mpr.c:551-576 sets `reallocating`; mpr.c:630-634 calls
     `mpr_iocfacts_free()`.
   - `mpr_iocfacts_free()` mpr.c:791-797 unconditionally
     `kfree(sc->commands, M_MPR)` — including the sleeper's still-owned `cm`,
     and `bus_dmamap_destroy()`s its map.
3. Sleeper wakes: lksleep returns 0 → mpr.c:3604 `if (error == EWOULDBLOCK)`
   is false → **no** `MPR_FLAGS_REALLOCATED` check on this path →
   `return (error)` with `*cmp` dangling.
4. Caller corruption sinks (traced, all reachable with the freed cm):
   - mpr_user.c:701-716: `rpl = cm->cm_reply`, then `RetFree:` →
     `mpr_free_command(sc, cm)` (mprvar.h:568-602) writes `cm->cm_flags`,
     `TAILQ_INSERT_TAIL(&sc->req_list, cm, …)` — inserting a pointer that
     (after the immediate same-size realloc at mpr.c:1522) aliases a *live*
     command of the new pool → two owners → kernel memory corruption.
   - mpr_sas_lsi.c:1173-1178: `callout_stop(&cm->cm_callout)` — UAF write.
   - mpr_user.c:846-868: `mprsas_free_tm(sc, cm)`.
5. Same hole in `mpr_request_polled()`: mpr.c:3641 loop condition reads
   `cm->cm_flags` every 50 ms; a concurrent reallocating reinit frees it
   mid-loop; the REALLOCATED check exists only on the 60 s timeout path
   (mpr.c:3662-3674).

## Exploit chain (postulated, hardware required)

slab UAF → `mpr_free_command()` on dangling cm inserts alias pointer into
`sc->req_list` → one `struct mpr_command` (with `cm_req` DMA frame, `cm_dmamap`,
`cm_callout`, function pointers `cm_complete`) owned twice → attacker-shaped
SCSI/ioctl command contents into `cm_req` frame while the kernel believes a
different operation owns it → controlled corruption of command state;
plausible path to arbitrary kernel write via `cm_sge`/`cm_req` reuse, not
demonstrated here (no hardware).

## Fix

`fix.diff` — generation counter (`sc->realloc_gen`) bumped on every
reallocating pass; both waiters snapshot it and treat a change as "command is
gone" (`*cmp = NULL`, ENXIO). Existing callers already handle `*cmp == NULL`.

## Cross-references

- DF-3800 — same realloc pass, stale `sassc->maxtargets` (filed on mpr_sas.c).
- DF-3660 (isp) — analogous "background context outlives teardown" class.

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