DF-4834 / verdict.json
{ "finding_id": "DF-4834", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED (harness; guest has no mrsas controller \u2014 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) \u2014 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.", "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 kernels :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 = kmallocd 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": "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.", "kernel_refs": [ "sys/dev/raid/mrsas/mrsas.c:1151", "sys/dev/raid/mrsas/mrsas_ioctl.c:153", "sys/dev/raid/mrsas/mrsas_ioctl.c:172", "sys/dev/raid/mrsas/mrsas_ioctl.c:186", "sys/dev/raid/mrsas/mrsas_ioctl.c:225", "sys/dev/raid/mrsas/mrsas_ioctl.c:307", "sys/dev/raid/mrsas/mrsas_ioctl.c:354", "sys/dev/raid/mrsas/mrsas.c:790" ], "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 kernels 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.", "attempts": 3, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 240, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -o df4834 df4834_harness.c -lpthread", "run_cmd": "./df4834", "code_hash": "fde37bd222ec43b2ee97bc47f63255077d817742bc187e354458b6153041f74d", "notes": "Guest has 4 GB RAM, no /dev/mrsas* (driver built as module in X86_64_GENERIC but no LSI hardware) \u2014 runtime trigger not possible; harness verification only, DF-4559 precedent. Knowns DF-1917 (sgl_off), DF-1918 (sense_off), DF-1919 (iov_len truncation), DF-1920 (uninit cleanup reads), DF-1921 (early-return leaks) are separate defects in the same function and remain as filed; this finding is the missing-copyin/validation-race class with a different sink (kernel stack arrays).", "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)", "fix_status": "fixed", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Validated 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.", "fix_evidence": "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." } |