# DF-4836 — frame→hdr.sge_count desync: firmware consumes stale SGEs beyond the driver-populated ones

- File: `sys/dev/raid/mrsas/mrsas_ioctl.c:205-225`
- Severity: Medium — confidence **likely** (driver-side divergence certain;
  firmware-side consequence follows the MFI protocol but is not runtime-verifiable
  without an LSI controller)
- **Not PoC-verified** (no mrsas hardware in the guest; the divergence is
  statically provable, the device-side impact is not demonstrable).

## Root cause

`mrsas_passthru()` validates **`user_ioc->sge_count`** against
`MAX_IOCTL_SGE (16)` (mrsas_ioctl.c:186) and populates exactly that many SGEs
into the frame at `frame + user_ioc->sgl_off` (:225-255). But the frame it
ships to the firmware is a **verbatim copy of the user's frame**
(:205 `memcpy(cmd->frame, user_ioc->frame.raw, 2 * MEGAMFI_FRAME_SIZE)`,
128 bytes into the 1024-byte `MRSAS_MFI_FRAME_SIZE` allocation), and the frame
header's own `sge_count` byte (mrsas.h:1913, offset 0x07) is **never validated
or overwritten** — only `context`/`pad_0` are fixed up (:206-207) and the SGL64
flag bits stripped (:208-209).

The firmware walks the frame's SGL per `frame->hdr.sge_count` (the whole MFI
passthrough design — `mrsas_build_mptmfi_passthru()` (mrsas.c:2820-2857) hands
the FW the raw `frame_phys_addr` as the SGL chain). If the caller sets
`hdr.sge_count > user_ioc->sge_count`:

- the extra SGEs the FW consumes are **stale pool memory** — zeroed only once
  at pool creation (mrsas_ioctl.c:471), then carrying whatever previous
  ioctls' SGLs left there: physical addresses of `bus_dmamem_free`d ioctl data
  buffers;
- with `hdr.sge_count = 255` the FW reads 255·8 = 2040 bytes of SGL from a
  1024-byte allocation — past the frame into adjacent kernel DMA memory.

Device-side consequences: DMA to/from freed kernel buffers (UAF by device) or
to garbage addresses parsed from adjacent memory — kernel memory corruption
mediated by the controller, triggered by the same root/operator ioctl surface
as DF-1917..DF-1919 (`/dev/mrsasN` is root:operator 0660).

## Suggested fix

Clamp/overwrite the shipped frame header to the validated count after the
copy:

```c
	cmd->frame->hdr.sge_count = user_ioc->sge_count;	/* == validated <= 16 */
```

(for non-SGL frame types set it to the user's value only when the frame type
has an SGL; at minimum, reject `hdr.sge_count > user_ioc->sge_count`).

## Artifacts

Static analysis only — no harness (the FW-side walk cannot be replicated
honestly). See the finding markdown for the full trace.
