# DF-4789 — mpr_print_sgl: unbounded SGE walk, NULL chain deref, unconditional 128-byte hexdump on firmware INVALID_SGL

## What

`sys/dev/raid/mpr/mpr_table.c:530-584` (`mpr_print_sgl`), reached **unconditionally**
(not gated on `sc->mpr_debug`) from `mpr_sas.c:2814` whenever the controller returns
`MPI2_IOCSTATUS_INVALID_SGL` for a completed SCSI IO:

1. `mpr_table.c:547` — `hexdump(frame, 128, NULL, 0)` assumes a ≥128-byte request
   frame; `cm->cm_req` points into the DMA request pool sized from IOC-facts
   `IOCRequestFrameSize` (firmware-reported). A smaller reported frame size makes the
   hexdump read past the frame into adjacent DMA heap and print it to console.
2. `mpr_table.c:548-572` (IEEE walk) — `ieee_sge++` with **no per-frame bound**: the walk
   continues until an SGE happens to carry END_OF_LIST. If the (driver-built) SGL is
   malformed — exactly the condition INVALID_SGL announces — the walk runs off the end
   of the request frame, reading and printing heap past it.
3. `mpr_table.c:557-568` — on a CHAIN_ELEMENT flag the walker takes
   `TAILQ_FIRST/TAILQ_NEXT(&cm->cm_chain_list)` and then dereferences `chain->chain`
   at `:568` **without checking `chain != NULL`**. If the frame claims more chain
   elements than the command actually has (or the command has none), this is a NULL
   pointer dereference → panic.
4. `mpr_table.c:573-583` (simple walk) — same unbounded `sge++` pattern.

Sibling context: pass-1 already filed the adjacent debug-path issues DF-3816
(dump_reqs sysctl unlocked walk) and DF-3817 (unbounded hexdump of event data) as Low;
this is the same class in the INVALID_SGL printer, which pass 1 did not cover.

## Trigger / threat

Triggered by the controller firmware returning `MPI2_IOCSTATUS_INVALID_SGL` on any CAM
SCSI IO completion (an unprivileged user generating disk I/O reaches this completion
path; the IOC status itself is firmware-produced). With a malicious/buggy IOC (or
corrupted DMA), this is an OOB heap read printed to console (info disclosure to anyone
who can read the console/syslog) and/or a NULL-deref panic (local DoS). No write
primitive. The walk data comes from the request the *driver* built, so on a healthy
controller+driver pair this never fires — firmware-gated, hence Low.

## Reproduce (requires LSI Fusion-MPT3 hardware — NOT available in the audit QEMU guest)

Not runnable on the audit guest (virtio-only; no vendor 0x1000 MPT3 device — see
`env.txt`). On hardware, a fault-injected IOC reply with IOCStatus=INVALID_SGL on a
command whose frame SGL has a chain flag but an empty `cm_chain_list` panics at
`mpr_table.c:568`.

## Fix

`fix.diff` — break out when the chain list is exhausted, and cap the per-frame SGE walk
so it can never run past a request frame.
