# DF-4834 — mrsas_passthru() raw userland pointer: validation→use TOCTOU walks past 16-entry stack arrays

- File: `sys/dev/raid/mrsas/mrsas_ioctl.c` (entry: `sys/dev/raid/mrsas/mrsas.c:1151`)
- Severity: High (kernel stack OOB write + OOB stack reads; root/operator-gated ioctl)
- Reproduced: **harness on the DragonFly guest** (no mrsas controller exists in the guest —
  same methodology as DF-4559/DF-4363).

## Root cause

`mrsas_ioctl()` (mrsas.c:1151) passes the **raw userland `a_data` pointer** into
`mrsas_passthru()`. Every field access in the passthrough then reads/writes user
memory directly, with no copyin:

- `mrsas_ioctl.c:186` validates `user_ioc->sge_count > MAX_IOCTL_SGE (16)` — a read of USER memory
- `mrsas_ioctl.c:225` `for (i = 0; i < user_ioc->sge_count; i++)` — **re-reads USER memory**, and the body writes `&ioctl_data_tag[i]`, `&ioctl_data_mem[i]`, `&ioctl_data_phys_addr[i]` — stack arrays declared `[MAX_IOCTL_SGE]` (=16) at `mrsas_ioctl.c:153-156`
- `mrsas_ioctl.c:307` the copyout loop re-reads `sge_count`/`sgl[]` a **second** time, after the multi-millisecond firmware command (`mrsas_issue_blocked_cmd`, :301) — a very wide race window; it reads `ioctl_data_mem[i]` OOB from the stack and uses the value as the `copyout` source
- `mrsas_ioctl.c:354` the cleanup loop re-reads a **third** time and calls `bus_dmamap_unload(ioctl_data_tag[i], ...)` with OOB-read garbage

A second thread of the ioctl caller (any process with the device open —
`/dev/mrsasN` is `root:operator 0660`, mrsas.c:790-792) flips `sge_count` after
validation → `i` runs toward 2^32-1 → **OOB write of kernel heap pointers at
`&ioctl_data_tag[i]` with a fully attacker-chosen offset** (8·i bytes past the
array — i.e., across `ida`-style adjacent locals, saved registers and the
return address of `mrsas_passthru`), OOB stack reads used as copyout sources
(kernel stack contents disclosed when the slot holds a valid kernel pointer,
`copyout` only validates the destination), and wild `bus_dmamap_unload` calls
in cleanup.

Additionally, because there is no copyin, an unmapped or read-only ioctl
argument makes the kernel fault on the first raw deref (`mrsas_ioctl.c:172`)
with no on-fault protection → kernel panic.

## Build / run (on the DragonFly guest)

```
cc -O2 -Wall -o df4834 df4834_harness.c -lpthread
./df4834
```

## Expected

- arm 1 (race at the validation→use boundary): SIGSEGV at the guard page
  standing in for the rest of the kernel stack frame; canary slots 16..~591
  (kernel: return-address/saved-regs region) overwritten with heap pointers.
- arm 2 (deterministic wide window; flip during the FW command): copyout loop
  reads `ioctl_data_mem[16..23]` from past the array — kernel stack values
  used as copyout sources.
- arm 3 (FIXED model — kernel copy of the packet): all loops stay in [0,16),
  no writes past the arrays.

See `run.log`, `VERDICT.md`, `fix.diff`.
