mrsas_passthru operates on the raw userland ioctl pointer: validationβuse TOCTOU on sge_count walks past the 16-entry stack arrays (kernel stack OOB write + OOB stack reads); unmapped arg panics the kernel
Summary
The ioctl layer hands the raw a_data pointer to mrsas_passthru with no copyin - every 'validation' reads memory the caller can mutate concurrently; three loops re-read sge_count/sgl[] from user memory writing/reading the [16] stack arrays, and the copyout loop runs after the multi-ms FW command (a very wide race window). A second thread flips sge_count after validation -> controlled-offset kernel stack OOB write (8*i bytes past the arrays, value = kmalloc'd tag pointers) across saved registers/return address; the copyout loop discloses kernel stack values; an unmapped arg faults the kernel with no on-fault handler. Harness: guard-page SIGSEGV with 576+ canary slots clobbered + deterministic copyout OOB reads + clean fixed model. Root/operator (0660). Copyin-at-entry fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4834 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 3.8 KB | β raw | |
| df4834_harness.c | β | 8.8 KB | view raw | |
| build.sh | β | 104 B | view raw | |
| run.sh | β | 89 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.2 KB | view raw | |
| manifest.json | β | 927 B | view raw | |
| verdict.json | β | 5.0 KB | view raw |
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:186validatesuser_ioc->sge_count > MAX_IOCTL_SGE (16)β a read of USER memorymrsas_ioctl.c:225for (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) atmrsas_ioctl.c:153-156mrsas_ioctl.c:307the copyout loop re-readssge_count/sgl[]a second time, after the multi-millisecond firmware command (mrsas_issue_blocked_cmd, :301) β a very wide race window; it readsioctl_data_mem[i]OOB from the stack and uses the value as thecopyoutsourcemrsas_ioctl.c:354the cleanup loop re-reads a third time and callsbus_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.
DF-4834 β VERDICT
REPRODUCED (harness; the guest has no LSI MegaRAID controller β same methodology as DF-4559/DF-4363).
The code path (traced)
mrsas_ioctl()βsys/dev/raid/mrsas/mrsas.c:1112-1159.arg = ap->a_data(line 1116) is the raw userland pointer ofioctl(2); line 1151 hands it tomrsas_passthru(sc, (void *)arg)with no copyin.mrsas_passthru()βsys/dev/raid/mrsas/mrsas_ioctl.c:148-370.user_ioctherefore points into user address space for the whole call: - :172in_cmd->dcmd.opcodeβ first raw user deref (unmapped arg β unprotected kernel page fault β panic; on SMAP-enabled CPUs every call faults). - :174user_ioc->frame.hdr.cmd_status = MFI_STAT_OKβ raw kernel write into user memory (RO mapping β panic). - :186 validationuser_ioc->sge_count > MAX_IOCTL_SGE (16)reads user mem. - :153-156 stack arraysioctl_data_tag[16] / ioctl_data_dmamap[16] / ioctl_data_mem[16] / ioctl_data_phys_addr[16]. - :225-264 the copyin loop re-readsuser_ioc->sge_countevery iteration and writes&ioctl_data_tag[i](:237),&ioctl_data_mem[i](:241-242),&ioctl_data_phys_addr[i](:246-248), pluskern_sge32[i](:254-255 β into the 1024-byte frame DMA block). - :301mrsas_issue_blocked_cmd()β the firmware round-trip (up to 180 s wait budget, MRSAS_INTERNAL_CMD_WAIT_TIME=180) β a very wide window. - :307-314 copyout loop β second re-read ofsge_count/sgl[]; readsioctl_data_mem[i]OOB and uses the stack value as thecopyoutsource (copyoutvalidates only the destination, sys/platform/pc64/x86_64/support.s:228-258 β a valid kernel pointer as source becomes a kernel-memory disclosure). - :354-364 cleanup loop β third re-read;bus_dmamap_unload(ioctl_data_tag[i], ...)with OOB-read stack garbage.- The ioctl is reachable by root and the
operatorgroup (make_dev(..., UID_ROOT, GID_OPERATOR, 0660), mrsas.c:790-792).
What the harness proves (run.log)
- arm 1 β flip
sge_count16β0x20000 right after the check: the loop writesioctl_data_tag[i]/ioctl_data_mem[i]out of bounds until it hits the guard page (SIGSEGV). Canary slots 16..591 recorded hits β in the kernel those slots are the rest ofmrsas_passthru's frame (adjacent locals, saved registers, return address) and the write value is a heap pointer (bus_dma_tag). Attacker-chosen offset: 8Β·i bytes, i up to 2^32-1. - arm 2 β deterministic mid-command flip: the copyout loop reads
ioctl_data_mem[16..23]past the array and uses the (kernel stack) values as copyout sources β the leak/wild-deref class, no tight race required. - arm 3 β kernel-copy model (the fix): racer is invisible, loops stay bounded, zero writes past the arrays.
Escalation ceiling (honest)
Primitive = controlled-offset kernel stack OOB write of kernel heap pointers +
OOB stack reads (disclosure when a valid pointer sits in the slot) + wild
bus_dmamap_unload on garbage. On this non-SMEP/no-KASLR target a full
stack-pivot chain is plausible, but the ioctl requires root/operator and the
guest has no mrsas device, so end-to-end uid=0 could not be demonstrated β
same limitation recorded for the pass-1 family (DF-1917..DF-1919, PR:L per
this audit's convention).
Fix
Copy the packet in at the ioctl entry and operate on the kernel copy
(fix.diff, kernel-copy model validated by harness arm 3).
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 (kernel copy of the packet at ioctl entry): racer writes to user memory are invisible to the kernel loops, all three loops stay in [0,16), zero writes past the arrays, clean exit. fix.diff not applied to the read-only sys/ tree; kernel-build validation not meaningful without an mrsas controller in the guest.
run.log arm 3: '[arm 3] FIXED model: kernel copy of the packet (copyin at entry) / loops stayed in [0,16): clean exit; writes past arrays: none'; fix.diff in pack.
Confirmed kernel references
Detail
Exploit chain
open /dev/mrsasN (root/operator) -> ioctl(MRSAS_IOC_FIRMWARE_PASS_THROUGH, arg) with sge_count=16 and non-empty sgl[] -> thread2 flips arg->sge_count to N right after the kernel's :186 check (or during the multi-ms FW command for the copyout/cleanup loops) -> mrsas_ioctl.c:225-264 writes &ioctl_data_tag[i] for i>=16 at attacker-chosen 8*i offset (value = kmalloc'd bus_dma_tag pointer) -> smash of mrsas_passthru frame (saved regs / return address) -> crash-grade kernel stack corruption; alt path: copyout loop OOB stack reads -> kernel stack disclosure / wild bus_dmamap_unload.
Evidence (decisive lines)
run.log: '[arm 1] -> SIGSEGV at guard page: ioctl_data_tag[i]/ioctl_data_mem[i] written OUT OF BOUNDS, i ran toward 0x20000', 'canary: first OOB slot=16 (byte +0 past the 16-entry arrays), last=591'; arm 2 shows copyout(ioctl_data_mem[16..23] = 0xfffff000... kernel stack values); arm 3 (fixed kernel-copy model) 'writes past arrays: none'. VERDICT.md has the full path:line trace.
PoC changes
No seed PoC (pass-2 new finding). Harness written from scratch: faithful packed replica of struct mrsas_iocpacket (mrsas_ioctl.h:80-92), the four MAX_IOCTL_SGE=16 stack arrays placed below a guard page, verbatim :225-264 loop with re-reading user-memory bound (volatile stands in for the kernel's opaque extern calls), pipe-handshake racer, deterministic mid-FW-command arm, fixed kernel-copy arm. One earlier local iteration hung (uninitialized pthread join on DFly) and one had a false-positive in the fixed-arm checker; both fixed before the recorded run.
Verified recommended fix
copyin the mrsas_iocpacket to a kernel struct in mrsas_ioctl() before mrsas_passthru() and copyout the updated header after (fix.diff in pack)
Verdict
REPRODUCED (harness; guest has no mrsas controller β same methodology as DF-4559/DF-4363). mrsas_ioctl() (mrsas.c:1151) passes the raw userland a_data pointer into mrsas_passthru() with no copyin; the :186 sge_count<=16 validation reads USER memory and the :225 copyin loop, :307 copyout loop and :354 cleanup loop each RE-read sge_count/sgl[] from user memory. A second thread of the caller (device is root:operator 0660, mrsas.c:790) flips sge_count after validation, so i walks toward 2^32-1 and bus_dma_tag_create/bus_dmamem_alloc write kernel heap pointers at &ioctl_data_tag[i]/&ioctl_data_mem[i]/&ioctl_data_phys_addr[i] past the 16-entry stack arrays (mrsas_ioctl.c:153-156) β controlled-offset kernel stack OOB write across saved registers/return address; the copyout loop additionally reads ioctl_data_mem[i] OOB and uses kernel stack values as copyout sources (disclosure when valid), and cleanup calls bus_dmamap_unload with garbage tags. An unmapped/read-only ioctl arg instead faults the kernel at the first raw deref (:172/:174) with no on-fault handler. Harness: guard-page arm SIGSEGVs with canary slots 16..591 clobbered; deterministic mid-FW-command arm shows the copyout OOB reads; kernel-copy (fix) model stays bounded and clean. uid=0 chain not demonstrable without the HBA; primitive is kernel stack corruption.
No comments yet.