# DF-4008 — mpr diag-buffer cross-type registration state confusion

## What

`MPTIOCTL_DIAG_ACTION` (REGISTER/UNREGISTER/READ_BUFFER) manages *three*
logical buffer types (`fw_diag_buffer_list[TRACE|SNAPSHOT|EXTENDED]`,
mprvar.h:440-441) but stores the *live* DMA resources in **one shared trio**
of softc fields — `sc->fw_diag_buffer`, `sc->fw_diag_busaddr`,
`sc->fw_diag_dmat`, `sc->fw_diag_map` (mprvar.h:410-413). `mpr_diag_register()`
(mpr_user.c:1442-1638) never checks that the shared trio is free before
overwriting it, and per-type state (`pBuffer->size`, `unique_id`) stays stale
afterwards. With two or more buffer types enabled by the IOC
(mpr.c:588-599 sets `enabled=TRUE` for *each* advertised capability — a
SAS3008-class FW advertises TRACE+SNAPSHOT+EXTENDED), an ioctl-capable user
can drive three distinct memory-safety failures:

1. **Stale-size OOB read / kernel memory disclosure**: register type A with
   `RequestedBufferSize = 1 MB`, then type B with 4 KB. `sc->fw_diag_buffer`
   now points at B's 4 KB allocation, but `list[A].size` is still 1 MB.
   `mpr_diag_read_buffer()` with A's `UniqueId` bounds-checks against the
   stale 1 MB (`off + bytes > pBuffer->size`, mpr_user.c:1798) and then
   `copyout`s from the 4 KB allocation (mpr_user.c:1812-1814) → up to
   ~1 MB of adjacent kernel memory disclosed to userspace.
2. **Cross-type free / tag leak**: `mpr_diag_unregister()` of A frees
   `sc->fw_diag_buffer/dmat` (mpr_user.c:1681-1694) — which are B's
   resources. B's registration state survives with freed backing.
3. **NULL-deref panic / UAF read**: any subsequent `READ_BUFFER` for B hits
   `bus_dmamap_sync(sc->fw_diag_dmat=NULL, ...)` (mpr_user.c:1805) → NULL
   pointer dereference panic; if the pointers were not reset, it is a
   `copyout` from freed DMA memory (UAF read).

## Trigger (privileged surface)

`/dev/mprN` is 0640 root:operator (mpr_user.c:205) — same boundary as
DF-1326. Sequence: REGISTER(TRACE,1MB) → REGISTER(SNAPSHOT,4KB) →
READ_BUFFER(uidA, 0, 1MB) → UNREGISTER(uidA) → READ_BUFFER(uidB,...).
`mpr_user_diag_action` serializes via MPR_FLAGS_BUSY (mpr_user.c:2000-2005),
so the confusion is fully deterministic — no race needed.

Hardware gate: the first REGISTER must survive
`mpr_post_fw_diag_buffer()` (needs real FW accepting DIAG_BUFFER_POST) and
the IOC must advertise ≥2 diag capabilities. Source-level mechanism is
otherwise unconditional.

## Replication (guest, userspace logic-level)

The state machine is pure kernel bookkeeping around bus_dma; `diag_statemachine.c`
replicates `mpr_diag_register` / `mpr_diag_unregister` / `mpr_diag_read_buffer`
verbatim modulo bus_dma stubs and runs the exact sequence above.
`replication_run.log` (guest, 6.5-DEVELOPMENT):

```
2) ... live_alloc now 4096 bytes, but TRACE.size is still 1048576
3) READ uid=0xA -> copyout OOB READ: bound check used stale size=1048576 but
   live allocation is 4096 bytes -> 1044480 bytes past the end disclosed to user
4) UNREGISTER uid=0xA -> shared trio freed ... this was SNAPSHOT's allocation
5) READ uid=0xB -> bus_dmamap_sync(NULL) => KERNEL NULL-DEREF PANIC here
```

## Fix sketch

Track the live allocation per buffer type (store `dmat/map/buffer/busaddr`
inside `mpr_fw_diagnostic_buffer_t`), or refuse REGISTER while any type holds
the shared trio; recompute `pBuffer->size` from the live allocation at
READ_BUFFER time.
