# DF-4835 — VERDICT

**REPRODUCED (harness; guest has no mrsas controller — DF-4559 methodology).**

## The code path (traced)

1. `mrsas_alloc_mfi_cmds()` — mrsas_ioctl.c:382-434. `mfi_cmd_list` is
   allocated and populated (:394-419), then `mrsas_create_frame_pool()`
   (:422).
2. `mrsas_create_frame_pool()` — mrsas_ioctl.c:446-477: creates the tag
   (:451-459) and allocates a 1024-byte DMA frame per command
   (`mrsas_alloc_frame` :486-503, `bus_dmamem_alloc` + `bus_dmamap_load`,
   both `BUS_DMA_NOWAIT`). A failure at command i returns ENOMEM with frames
   0..i-1 alive.
3. The error path at mrsas_ioctl.c:424-427 runs `mrsas_free_frame()` on all
   32 commands. `mrsas_free_frame()` (:532-538) does
   `if (frame_phys_addr) bus_dmamap_unload(...)`, `if (frame_mem)
   bus_dmamem_free(...)` and **clears nothing** — every allocated cmd keeps
   dangling non-NULL `frame_mem`/`frame_phys_addr`.
4. :428-429 destroys `sc->mficmd_frame_tag` (DFly `bus_dma_tag_destroy` frees
   the tag object — busdma_machdep.c:334-346 — map_count is 0 because
   `bus_dmamem_alloc` maps are not counted) and the pointer is **not NULLed**.
5. `mrsas_init_fw()` — mrsas.c:1719-1722 returns failure.
6. `mrsas_attach()` — mrsas.c:800 `goto attach_fail_fw` → :842
   `mrsas_free_mem()`.
7. `mrsas_free_mem()` — mrsas.c:1011-1018: `mfi_cmd_list` is non-NULL, so it
   calls `mrsas_free_frame()` on every command **again** →
   `bus_dmamap_unload(freed_tag, ...)` (use-after-free of the tag object) and
   `bus_dmamem_free(freed_tag, freed_frame_mem, ...)` — **double free of each
   frame**; then :1017-1018 `bus_dma_tag_destroy` **again** — double free of
   `dmat->segments` and the tag object itself.

Reachability: requires a `BUS_DMA_NOWAIT` allocation failure at driver attach
(kernel memory pressure while the device probes / module loads) — not directly
attacker-controlled, hence Low severity, same bucket as the pass-1 attach
double-free family (DF-4438).

## What the harness proves (run.log)

Baseline (verbatim sequence, failure injected at cmd #5):
- 5× `** USE-AFTER-FREE touch of freed <tag>` (per-frame unload through the
  destroyed tag),
- 5× `** DOUBLE FREE of <frame>` (the frames freed at step 3 freed again at
  step 7),
- 1× `** DOUBLE FREE of <tag>` (double `bus_dma_tag_destroy`),
- `mrsas_free_mem: HEAP CORRUPTION`, child exit 42.

Fixed model (error path leaves the frames/tag alive for `mrsas_free_mem`, per
`fix.diff`): `mrsas_free_mem: clean`, exit 0.

## Fix

Do not free the frames or destroy the tag in the error path — the caller
already unwinds via `mrsas_free_mem()` (`fix.diff`).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
