# DF-4868 — mps_print_sgl: unbounded SGL walk, NULL chain deref, fixed 128-byte hexdumps — unconditional on firmware MPI2_IOCSTATUS_INVALID_SGL

## What

`sys/dev/raid/mps/mps_table.c:449-487` (`mps_print_sgl`), entered via
`mps_print_scsiio_cmd` (`mps_table.c:489-496`, which passes the fw/driver-written
`req->SGLOffset0` as the byte offset), reached **unconditionally and ungated** (raw
`kprintf`/`hexdump`; no `sc->mps_debug` check anywhere in the function) from
`sys/dev/raid/mps/mps_sas.c:2063` whenever the controller returns
`MPI2_IOCSTATUS_INVALID_SGL` for a completed SCSI IO:

1. `mps_table.c:462` — `hexdump(frame, 128, NULL, 0)` assumes a ≥128-byte request
   frame. `cm->cm_req` points into the DMA request pool whose per-frame size is the
   IOC-facts `IOCRequestFrameSize * 4` (`mps.c:750`, `mps.c:812-823`) — a
   firmware-reported value, not guaranteed ≥128 bytes.
2. `mps_table.c:463-472` — `while (frame != NULL)` walk with `sge++` and **no
   per-frame bound**: the loop terminates only when an SGE happens to carry
   END_OF_LIST (0x01) or END_OF_BUFFER (0x40). On the INVALID_SGL path the SGL is by
   definition malformed — the walk runs off the end of the request frame, reading and
   printing adjacent request-frame heap to the console (each `seg%d` line prints
   attacker-visible kernel heap content: flags, length, 64-bit addresses).
3. `mps_table.c:473-485` — on a LAST_ELEMENT (0x80) flag the walker takes
   `TAILQ_FIRST`/`TAILQ_NEXT` of `cm->cm_chain_list` (`:478-481`) and then
   dereferences `chain->chain` at `:482` **without a NULL check**. `cm_chain_list` is
   initialized empty per command (`mps.c:859`) and only gains entries when the SGL
   spills into a chain (`mps.c:1808`); any command whose I/O fit in the request
   frame has an empty list, so one stale/garbage LAST_ELEMENT flag bit in the walked
   memory is an instant NULL-deref panic.
4. `mps_table.c:484` — second fixed `hexdump(frame, 128, ...)` on the chain frame,
   which is likewise `IOCRequestFrameSize * 4` bytes (`mps.c:772`, `mps.c:821-823`).

Unlike its mpr sibling (DF-4789), where the chain deref exists only in the IEEE-SGE
walk, the mps driver has a single walk — so **every** INVALID_SGL completion reaches
the chain-deref shape; a simple-SGE command with an empty chain list is the common
case.

## Trigger / threat

Triggered by controller firmware returning `MPI2_IOCSTATUS_INVALID_SGL` on any CAM
SCSI IO completion (an unprivileged local user generating disk I/O rides this
completion path; the IOC status itself is firmware-produced — by a buggy/malicious
IOC or by DMA corruption of the driver-built request). Consequences: OOB heap read
printed to console (info disclosure to anyone who can read console/syslog), or a
NULL-pointer panic (local DoS). No write primitive; the walked SGL is driver-built,
so on a healthy controller+driver pair this never fires — firmware-gated, hence Low
(severity aligned with the mpr twin DF-4789 and pass-1 DF-3816/DF-3817 class).

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

Not runnable on the audit guest (virtio-only; no vendor 0x1000 MPT2 device, driver
never attached — see `env.txt`). On hardware, a fault-injected IOC reply with
IOCStatus=INVALID_SGL for a small (single-frame, chainless) command whose frame
contains a stale 0x80 flag byte in the flags dword of walked memory panics at
`mps_table.c:482`.

## Fix

`fix.diff` — bound the hexdumps to the actual IOC-facts frame size, cap SGEs walked
per frame, and break out when the chain list is exhausted (NULL guard before
`chain->chain`).
