β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4835

create_frame_pool failure path frees frames and destroys the tag while leaving every pointer dangling: attach unwind mrsas_free_mem() double-frees all frames and the tag

Summary

On frame-pool failure the error path frees frames/destroys the tag without clearing any state, then the caller's canonical unwind (mrsas_free_mem) frees everything again - bus_dmamap_unload through the freed tag (UAF), bus_dmamem_free double-frees per frame, double tag destroy. Harness: 5x frame double-frees + tag UAF + double-destroy ('HEAP CORRUPTION'); fixed model clean. OOM-gated attach-time multi-object double-free. Leave-cleanup-to-caller fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4835 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 2.9 KB ↓ raw
df4835_harness.c β€” 6.8 KB view raw
build.sh β€” 94 B view raw
run.sh β€” 107 B view raw
build.log β€” 99 B view raw
run.log β€” 1.4 KB view raw
env.txt β€” 242 B view raw
fix.diff β€” 1.0 KB view raw
manifest.json β€” 921 B view raw
verdict.json β€” 4.1 KB view raw

DF-4835 β€” mrsas frame-pool failure path frees frames + destroys tag; attach unwind double-frees them

  • File: sys/dev/raid/mrsas/mrsas_ioctl.c:422-431 (+ mrsas_free_frame :532-538; consequence site mrsas.c:1011-1018 via mrsas.c:800,842)
  • Severity: Low (attach-time, OOM-gated double-free; same bucket as DF-4438)
  • Reproduced: harness on the DragonFly guest (no mrsas hardware in guest).

Root cause

mrsas_alloc_mfi_cmds() calls mrsas_create_frame_pool() (mrsas_ioctl.c:422). On partial failure (a bus_dmamem_alloc/bus_dmamap_load fails for one of the 32 MFI commands β€” kernel OOM at attach, all allocations are BUS_DMA_NOWAIT), the error path:

  1. calls mrsas_free_frame() on all commands (:424-427) β€” which unloads and frees cmd->frame_mem but never clears frame_mem/frame_phys_addr (:532-538), and
  2. destroys sc->mficmd_frame_tag (:428-429) without NULLing it, and
  3. returns ENOMEM leaving sc->mfi_cmd_list fully populated.

mrsas_init_fw() propagates the failure (mrsas.c:1719-1722) β†’ mrsas_attach() unwinds via attach_fail_fw: β†’ mrsas_free_mem() (mrsas.c:800,842), which:

  1. calls mrsas_free_frame() on every command again (mrsas.c:1011-1016) β†’ bus_dmamap_unload through the destroyed (freed) tag, then bus_dmamem_free of the already-freed frame memory β€” double free, and
  2. destroys sc->mficmd_frame_tag again (mrsas.c:1017-1018) β€” double bus_dma_tag_destroy (double kfree(dmat->segments) + kfree(dmat)).

Build / run (on the DragonFly guest)

cc -O2 -Wall -o df4835 df4835_harness.c
./df4835          # baseline: verbatim kernel sequence
./df4835 fixed    # fixed model: error path leaves cleanup to mrsas_free_mem

Expected

  • baseline: ** USE-AFTER-FREE touch of freed <tag> per frame (unload through the destroyed tag), ** DOUBLE FREE of <frame> for each allocated frame (5 with the injected failure at cmd #5), final ** DOUBLE FREE of the tag, child exit 42.
  • fixed: mrsas_free_mem: clean, exit 0.

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

VERDICT.md
↓ download raw

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.

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

Validated with the harness fixed-model arm (error path leaves frames+tag alive; mrsas_free_mem performs the single free pass): 'mrsas_free_mem: clean ... exit=0' vs baseline exit=42 with 11 corruption events. fix.diff not applied to the read-only sys/ tree; kernel-build validation not meaningful without an mrsas controller in the guest.

run.log FIXED section: 'mrsas_free_mem: clean (use-after-free/double-free detected: no)' / '[FIXED ] child exit=0'; fix.diff in pack.
↓ fix.diffper-fix-DF-4835

Confirmed kernel references

Detail

Exploit chain

driver attach under kernel memory pressure -> mrsas_create_frame_pool bus_dmamem_alloc/load NOWAIT failure at cmd i -> error path frees frames 0..i-1 + destroys tag, pointers left dangling -> mrsas_free_mem second pass double-frees each frame and the tag -> kernel heap corruption (panic-grade); exploitability limited: requires OOM at attach and heap grooming between the two free passes.

Evidence (decisive lines)

run.log: '** USE-AFTER-FREE touch of freed 0x8004908c0' (tag, per frame), '** DOUBLE FREE of 0x8004a8400/0x8004a8000/0x8004a7c00/0x8004a7800/0x8004a7400' (frames 0..4), '** DOUBLE FREE of 0x8004908c0' (tag), 'mrsas_free_mem: HEAP CORRUPTION ... exit=42'; FIXED arm 'mrsas_free_mem: clean', exit 0. VERDICT.md has the path:line trace.

PoC changes

No seed PoC (pass-2 new finding). Harness written from scratch; DFly's jemalloc does not abort on double free, so the mock allocator tracks freed pointers explicitly (kfree_real/kmcheck) to detect every double-free/UAF deterministically; failure injected at cmd #5.

Verified recommended fix

In the create_frame_pool failure path, do not free frames or destroy the tag β€” leave them for the caller's mrsas_free_mem() unwind (fix.diff in pack)

Verdict

REPRODUCED (harness; guest has no mrsas controller β€” DF-4559 methodology). mrsas_alloc_mfi_cmds()'s create_frame_pool failure path (mrsas_ioctl.c:424-431) frees every allocated MFI frame via mrsas_free_frame() β€” which never clears cmd->frame_mem/frame_phys_addr (:532-538) β€” and destroys sc->mficmd_frame_tag without NULLing it, leaving sc->mfi_cmd_list populated with dangling pointers, then returns ENOMEM. mrsas_init_fw (mrsas.c:1719) propagates, attach unwinds via attach_fail_fw (:800,:842) into mrsas_free_mem(), which calls mrsas_free_frame() on all 32 cmds again (mrsas.c:1011-1016): bus_dmamap_unload through the freed tag (UAF) + bus_dmamem_free of already-freed frame memory (double free per frame), then a second bus_dma_tag_destroy (:1017-1018, double kfree of segments+tag). Harness replicates the verbatim sequence with a freed-tracking allocator: baseline reports 5 UAF tag touches, 5 frame double-frees and the tag double-free ('HEAP CORRUPTION', exit 42); fixed model (error path leaves cleanup to mrsas_free_mem) runs clean. Attach-time, OOM-gated (BUS_DMA_NOWAIT failure) β€” Low severity per DF-4438 convention, but a genuine kernel heap double-free chain.