DF-5519 / verdict.json
{ "finding_id": "DF-5519", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "On the stock production kernel (X86_64_GENERIC, no DIAGNOSTIC) every DMA channel-number check in isa_dma.c is compiled out. KLD proof: isa_dma_acquire(34) returned EBUSY via 1<<34 wrapping onto bit 2 and isa_dma_release(34) then cleared the live channel-2 inuse bit; isa_dma_acquire(9) accepted chan>=8; isa_dma_init(8,0x1000) performed the OOB static writes (dma_bouncebufsize[8] onto dma_bouncebuf[0]s low 32 bits, dma_bouncebuf[8] heap pointer onto ram_devclass) forging channel 0s bounce pointer, and the following isa_dmastart bounce bcopy wrote kernel data to the forged address 0xffffffff00001000 (fault VA low half == the bouncebufsize parameter) -- Fatal trap 12 supervisor write, captured. Reachability is driver/misconfig-gated (chan from rman DRQ start; isa_set_resource never bounds it; PnP masks are byte-wide so in-tree PnP yields 0..7; hints/KLD are privileged), so no unprivileged in-tree trigger exists on this guest (no fdc/ppc devices) -- but the primitive is full parameter-steered kernel memory corruption for any context that can name a DMA channel.", "exploit_chain": "isa_dma_init(0,1024) arms victim channel 0 -> isa_dma_init(8,V) OOB write forges dma_bouncebuf[0]=orig_high32:V and clobbers ram_devclass with a heap pointer -> isa_dmastart(WRITE,buf,n,0) bounce-path bcopy writes n controlled bytes to the forged VA (demonstrated: panic at 0xffffffff00001000 with V=0x1000); 1<<chan wrap additionally lets a bogus channel free/live-corrupt another drivers channel state. uid0 escalation not pursued: trigger requires privileged/buggy-driver channel value; no unprivileged in-tree path supplies chan>7 on this guest.", "evidence": "run.log (wrap-alias + chan>=8 acceptance, guest alive); panic.txt (Fatal trap 12, supervisor WRITE, VA 0xffffffff00001000 = original ptr high32 | bouncebufsize param 0x1000, Stopped at memmove+0x2b inside isa_dmastarts bcopy); env.txt (nm layout: &dma_bouncebufsize[8]==&dma_bouncebuf[0], &dma_bouncebuf[8]==&ram_devclass); fix_run.log (patched kernel: EINVAL everywhere, no panic).", "kernel_refs": [ "sys/bus/isa/x86_64/isa_dma.c:98-104", "sys/bus/isa/x86_64/isa_dma.c:127-128", "sys/bus/isa/x86_64/isa_dma.c:140-150", "sys/bus/isa/x86_64/isa_dma.c:162-181", "sys/bus/isa/x86_64/isa_dma.c:191-194", "sys/bus/isa/x86_64/isa_dma.c:217-227", "sys/bus/isa/x86_64/isa_dma.c:239-252", "sys/bus/isa/x86_64/isa_dma.c:288", "sys/bus/isa/x86_64/isa_dma.c:321", "sys/bus/isa/x86_64/isa_dma.c:336-342", "sys/bus/isa/x86_64/isa_dma.c:428-445", "sys/bus/isa/x86_64/isa_dma.c:483-492", "sys/bus/isa/isavar.h:174-182", "sys/dev/disk/fd/fd.c:620", "sys/dev/misc/ppc/ppc.c:1891", "sys/bus/isa/isa_common.c:884-908" ], "poc_changes": "Lead was analysis-only; built staged KLD harnesses (clean + fatal), confirmed .bss aliasing with nm before choosing chan=8, added sys/bus.h include for isa_if.h device_t, generated isa_if/bus_if/device_if headers and the dragonfly symlink for out-of-tree bsd.kmod.mk builds, split fatal/non-fatal runs to keep clean evidence.", "attempts": 5, "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": 3900, "guest_dirty": 0, "build_cmd": "cd /root/poc && make && make -f Makefile.p", "run_cmd": "kldload /root/poc/df5519.ko; kldunload df5519; kldload /root/poc/df5519p.ko", "code_hash": "df5519.c sha256=a6404e507438b4a2742bcf772ea9fc39c680ee960781dc7b56a3b764b3a5d076; df5519_panic.c sha256=cc11b9e43b4feaac30388b31ce31ac60338e5ac1a1faf3d7f87e068f235f9cd3", "notes": "isa_dmastatus/isa_dmastop had no validation at all (not even DIAGNOSTIC). Unlocked RMW on dma_busy/dma_inuse/dma_bounced noted (cross-driver races only; both ISA DMA drivers absent from modern configs) -- folded into this findings threat, not filed separately. Driver-side contract leads recorded for other files: ppc.c:1697+1949 (user len > 1024B bounce -> isa_dma.c:244 panic) and fd.c:1846+800 (FD_FORM b_bcount=7*nsecs > 1024B bounce -> same panic; nsecs>=147, ioctl_fd.h idfields[]).", "recommended_fix": "Make the chan bounds check unconditional in all eight entry points (EINVAL/early-return/-1; keep double-init panic), matching FreeBSDs unconditional checks.", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "Patched nativekernel of X86_64_GENERIC (fix.diff applied in guest /usr/src, combined with DF-5520s clamp in the same file): acquire(9)/acquire(34)/init(8) all return EINVAL with out of range on console; release(34) no longer clears chan 2; the forged-pointer bcopy panic is gone (dmastart used the real bounce buffer); guest survives every module. Baseline panics documented in panic.txt.", "fix_evidence": "findings/poc/DF-5519/fix_run.log (boot A + boot B sections); kernel #1 Tue Oct 6 20:57:57 UTC 2026" } |