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)
PoC verification
Evidence pack
findings/poc/DF-4835 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 sitemrsas.c:1011-1018viamrsas.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:
- calls
mrsas_free_frame()on all commands (:424-427) β which unloads and freescmd->frame_membut never clearsframe_mem/frame_phys_addr(:532-538), and - destroys
sc->mficmd_frame_tag(:428-429) without NULLing it, and - returns ENOMEM leaving
sc->mfi_cmd_listfully 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:
- calls
mrsas_free_frame()on every command again (mrsas.c:1011-1016) βbus_dmamap_unloadthrough the destroyed (freed) tag, thenbus_dmamem_freeof the already-freed frame memory β double free, and - destroys
sc->mficmd_frame_tagagain (mrsas.c:1017-1018) β doublebus_dma_tag_destroy(doublekfree(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 FREEof the tag, child exit 42. - fixed:
mrsas_free_mem: clean, exit 0.
See run.log, VERDICT.md, fix.diff.
DF-4835 β VERDICT
REPRODUCED (harness; guest has no mrsas controller β DF-4559 methodology).
The code path (traced)
mrsas_alloc_mfi_cmds()β mrsas_ioctl.c:382-434.mfi_cmd_listis allocated and populated (:394-419), thenmrsas_create_frame_pool()(:422).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, bothBUS_DMA_NOWAIT). A failure at command i returns ENOMEM with frames 0..i-1 alive.- The error path at mrsas_ioctl.c:424-427 runs
mrsas_free_frame()on all 32 commands.mrsas_free_frame()(:532-538) doesif (frame_phys_addr) bus_dmamap_unload(...),if (frame_mem) bus_dmamem_free(...)and clears nothing β every allocated cmd keeps dangling non-NULLframe_mem/frame_phys_addr. - :428-429 destroys
sc->mficmd_frame_tag(DFlybus_dma_tag_destroyfrees the tag object β busdma_machdep.c:334-346 β map_count is 0 becausebus_dmamem_allocmaps are not counted) and the pointer is not NULLed. mrsas_init_fw()β mrsas.c:1719-1722 returns failure.mrsas_attach()β mrsas.c:800goto attach_fail_fwβ :842mrsas_free_mem().mrsas_free_mem()β mrsas.c:1011-1018:mfi_cmd_listis non-NULL, so it callsmrsas_free_frame()on every command again βbus_dmamap_unload(freed_tag, ...)(use-after-free of the tag object) andbus_dmamem_free(freed_tag, freed_frame_mem, ...)β double free of each frame; then :1017-1018bus_dma_tag_destroyagain β double free ofdmat->segmentsand 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
fixedValidated 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.
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.
No comments yet.