# DF-4008 — VERDICT ## Verdict **INCONCLUSIVE (live) — mechanism certain at source level; live trigger requires an LSI SAS controller (IOC advertising ≥2 FW-diag buffer capabilities and accepting DIAG_BUFFER_POST), absent from this QEMU guest.** A verbatim userspace replication of the three state functions on the guest demonstrates all three failure transitions (stale-size 1,044,480- byte OOB disclosure, cross-type free, NULL-dmat panic point). The bug is in pure kernel bookkeeping — no race, fully deterministic, serialized by MPR_FLAGS_BUSY. ## Root cause (path:line) - `struct mpr_softc` keeps ONE shared diag resource trio (sys/dev/raid/mpr/mprvar.h:410-413) for THREE logical buffer types (`fw_diag_buffer_list[MPI2_DIAG_BUF_TYPE_COUNT=3]`, mprvar.h:440-441). - `mpr_diag_register()` (mpr_user.c:1504-1531) unconditionally runs `bus_dma_tag_create(..., &sc->fw_diag_dmat)` and `bus_dmamem_alloc(..., &sc->fw_diag_buffer, ..., &sc->fw_diag_map)` with **no check that the trio is already in use by another type**; the previous tag/buffer pointers are simply overwritten (leak). Per-type `pBuffer->size` (mpr_user.c:1596) retains the old, larger size. - `mpr_diag_read_buffer()` bounds-checks `StartingOffset + BytesToRead` against **the stale per-type size** (mpr_user.c:1798-1802) but reads from **the shared current allocation** `sc->fw_diag_buffer + StartingOffset` (mpr_user.c:1812-1814) → OOB `copyout` to userspace. - `mpr_diag_unregister()` (mpr_user.c:1681-1694) frees whatever the shared trio currently references (the *other* type's memory) while resetting only the unregistered type's `unique_id`. - A subsequent READ_BUFFER of the surviving type dereferences the freed/NULL `sc->fw_diag_dmat` at `bus_dmamap_sync` (mpr_user.c:1805) → NULL-deref panic, or reads freed memory (UAF) at :1812. Enabling condition: `mpr.c:588-599` sets `enabled = TRUE` for every diag capability bit the IOC reports — SAS3008-class FW reports TRACE, SNAPSHOT *and* EXTENDED, so ≥2 simultaneously enabled types is the common case on real hardware. ## Reachability `MPTIOCTL_DIAG_ACTION` (mpr_user.c:2365-2373) → `mpr_user_diag_action()` (:1992) → `mpr_do_diag_action()` (:1881) → register/unregister/read. Device 0640 root:operator (mpr_user.c:205); ioctls not gated on open mode (group perm r-- suffices, proven for the same node mode in DF-1326's replica run). No HW-independent prefix reaches the bug: REGISTER requires a successful `mpr_post_fw_diag_buffer()` (mpr_user.c:1612, real FW round-trip) before the state that matters is installed. ## Impact assessment - OOB read is a **disclosure** primitive: attacker-sized (first registration size is user-chosen, bounded by bus_dma_tag maxsize = that same value), repeated, serialized, arbitrary-offset within the stale size — kernel heap disclosure to an operator-group user. - NULL-deref is a reliable **panic/DoS**. - Cross-type free is a **leak + logical corruption** enabler. No write primitive found in this path (the walk only reads; post/release write only FW-controlled request frames). Ceiling: kernel info leak + DoS for group-operator; not by itself a privesc write primitive. ## Verification posture - Guest: no mpr device (`ls /dev/mpr*` → ENOENT; `pciconf -l` → no LSI/SAS). - `diag_statemachine.c` (this pack) replays the exact register/register/ read/unregister/read sequence with the three functions ported verbatim (bus_dma stubbed); run on the guest (see `replication_run.log`): step 3 prints the 1,044,480-byte stale-size OOB disclosure, step 4 the cross-type free, step 5 the NULL-dmat panic point. - `attempts`: 1 (logic replication deterministic). ## Recommended fix ```diff --- a/sys/dev/raid/mpr/mprvar.h +++ b/sys/dev/raid/mpr/mprvar.h @@ typedef struct mpr_fw_diagnostic_buffer { uint32_t unique_id; + bus_dma_tag_t dmat; /* per-type, not shared */ + bus_dmamap_t map; + uint8_t *buffer; + bus_addr_t busaddr; boolean_t enabled; ``` (and move the trio operations in mpr_user.c:1508-1633 / 1681-1694 / 1805-1814 onto `pBuffer->dmat/map/buffer/busaddr`; alternatively reject REGISTER while `sc->fw_diag_buffer != NULL`.) ## 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. (No fix.diff for this item: documented recommendation only - see Fix above.)