# DF-3816 — mpr_dump_reqs sysctl: unlocked walk of live command/chain lists (UAF read) and fixed 128-byte reads past frame allocations

## What

`mpr_dump_reqs()` (sys/dev/raid/mpr/mpr.c:1875-1927) is the
`dev.mpr.N.dump_reqs` sysctl (CTLFLAG_SKIP, so hidden but callable), gated by
`caps_priv_check_self(SYSCAP_NODRIVER)` — privileged-only.

```c
/* Best effort, no locking */                                    /* mpr.c:1904 */
for (i = smid; i < numreqs; i++) {
        cm = &sc->commands[i];
        if (cm->cm_state != state)
                continue;
        ...
        TAILQ_FOREACH_MUTABLE(chain, &cm->cm_chain_list, chain_link, chain1)
                hdr.numframes++;
        sbuf_bcat(sb, cm->cm_req, 128);                          /* mpr.c:1918 */
        TAILQ_FOREACH_MUTABLE(chain, &cm->cm_chain_list, chain_link, chain1)
                sbuf_bcat(sb, chain->chain, 128);                /* mpr.c:1921 */
}
```

Two defects:

1. **Unlocked concurrent access**: the command state machine, `cm_chain_list`
   mutation (chains are attached/detached under `sc->mpr_lock` by the
   completion path, mprvar.h:592-595) and the command completion are all
   lock-protected everywhere else; this handler reads them with no lock.
   A command completing concurrently on the interrupt path frees its chains
   into `sc->chain_list` while we walk `cm->cm_chain_list` → TAILQ traversal
   over freed elements (use-after-free reads) or over a command that flips
   state mid-dump → stale/garbage `hdr` and leaked heap contents into the
   sysctl return buffer (privileged info leak; contents are attacker-shaped
   only insofar as freed slab data is). Panic possible via corrupted TAILQ
   pointers.
2. **Fixed 128-byte copies**: `sbuf_bcat(sb, cm->cm_req, 128)` assumes
   request frames are ≥128 bytes, but frame size is
   `sc->facts->IOCRequestFrameSize * 4` (mpr.c:387) — device-reported.
   For the *last* frame (`i == num_reqs-1`) a frame size < 128 reads past
   the end of the `req_frames` DMA allocation; same for
   `chain->chain`/`sc->chain_frame_size` (= IOCMaxChainSegmentSize*16,
   can legitimately be 32/48/…).

## Threat

Privileged-only trigger (root-equivalent syscap) racing the storage
completion path → Low. Value: hardening + correctness for the debugging
facility.

## Not runnable here

No SAS3 controller on the audit guest → `dev.mpr.N` tree doesn't even
instantiate (probe.log).

## Fix

Take `mpr_lock` around the walk and clamp each copy to
`min(reqframesz,128)` / `min(chain_frame_size,128)` — see fix.diff.
