# DF-3818 — NVMe PRP page pool exhaustion: NULL-deref / KASSERT-panic under high queue depth (unprivileged local DoS)

## What

Two call sites grab a page from the fixed-size PRP pool and dereference it
with only an assertion as protection:

```c
/* mpr_build_nvme_prp() — sys/dev/raid/mpr/mpr.c:2716-2720 (ioctl path) */
prp_page_info = mpr_alloc_prp_page(sc);
KASSERT(prp_page_info != NULL, ("%s: There are no PRP Pages left ..."));
prp_page = (uint64_t *)prp_page_info->prp_page;      /* NULL deref */

/* mpr_check_pcie_native_sgl() — mpr.c:2988-2991 (I/O path) */
prp_page_info = mpr_alloc_prp_page(sc);
KASSERT(prp_page_info != NULL, ("%s: There are no PRP Pages left ..."));
curr_buff = (uint32_t *)prp_page_info->prp_page;     /* NULL deref */
```

The pool is fixed at `NVME_QDEPTH = MPR_REQ_FRAMES = 2048` pages
(mprvar.h:40,59; allocated in `mpr_alloc_nvme_prp_pages()`, mpr.c:1593-1631),
while the command pool can hold `num_reqs = MIN(max_reqframes,RequestCredit) +
MIN(max_prireqframes,HighPriorityCredit)` = up to **2048 + 128 = 2176**
concurrent commands (mpr.c:375-381). Every NVMe-target command that needs a
native SGL (`mpr_data_cb()` → `mpr_check_pcie_native_sgl()`, mpr.c:3483-3487)
holds one PRP page for the command's lifetime. With ≥ 2048 such commands in
flight, the 2049th `mpr_alloc_prp_page()` returns NULL and:

- on kernels with INVARIANTS (X86_64_GENERIC ships it, sys/config line 56):
  `panic("mpr_check_pcie_native_sgl: There are no PRP Pages left ...")`;
- on kernels without: `KASSERT` is a no-op (sys/sys/systm.h:117) →
  `prp_page_info->prp_page` reads at offset-of-field from NULL → kernel
  page-fault panic.

Either way: kernel panic = local DoS.

## Threat

Unprivileged users can open the raw block devices exposed for NVMe drives
attached through the SAS3 controller and drive queue depth above 2048
(many threads / large async I/O chains). Requires the (supported but
uncommon) NVMe-behind-MPT3 configuration. No memory corruption: pure DoS.
Severity Medium per the local-DoS rubric row; confidence speculative on
achievability (needs ≥2049 concurrent native-SGL NVMe I/Os), certain on the
code defect.

## Not runnable here

No SAS3 controller (and hence no NVMe-behind-it) on the audit guest
(probe.log).

## Fix

`fix.diff`: `mpr_check_pcie_native_sgl()` falls back to a normal IEEE SGL
(`return 1` — its existing "not built here" contract); `mpr_build_nvme_prp()`
returns without touching NULL (the FW will reject the malformed request —
degraded but non-fatal), plus a proper fix note (make it int-returning).
