DF-4569 / verdict.json
{ "finding_id": "DF-4569", "status": "inconclusive", "reproduced": 0, "impact": "panic", "confidence": "certain", "verdict": "Root cause certain and kernel-side machinery reproduced on the guest: drm_ioctl() dispatches on cmd&0xff only and never validates the user command against the canonical ioctl->cmd (upstream v4.7s drv_size/usize/asize + cmd=ioctl->cmd logic was dropped by the port), while mapped_ioctl() sizes the handler buffer from the users IOCPARM_LEN/dir bits. Via an exact-semantics replica module, an unprivileged user demonstrated (1) drm_getstats-style memset(248) over the 128-byte on-stack buffer -> return address zeroed -> deterministic fatal kernel-mode page fault at RIP=0 (panic.txt), and (2) drm_getmagic-style 4-byte-write+return-0 handler turning the IOC_VOID|IOC_OUT cmd into an up-to-8KB kernel stack disclosure (27-30 kernel pointer qwords per run, varying across runs). The final DRM-specific hop (nr -> drm_getstats/drm_getmagic through the real table) is proven statically (drm_ioctl.c:215-224, 573, 577; drm_auth.c:58-76) but could not execute on this guest because it has no DRM device (QEMU std VGA, no /dev/dri, DRM not built into X86_64_GENERIC) -- hence inconclusive rather than reproduced.", "exploit_chain": "open /dev/dri/cardN (root:video 0660; standard desktop session user) -> ioctl(fd, IOC_OUT|(8<<16)|(d<<8)|0x06) -> mapped_ioctl hands drm_ioctl the 128-byte stack buffer -> drm_getstats memsets sizeof(struct drm_stats)=248 -> 120-byte kernel stack overflow, RIP=0 panic. Leak variant: ioctl(fd, IOC_VOID|IOC_OUT|(size<<16)|0x02) -> drm_getmagic returns 0 -> mapped_ioctl copyouts `size bytes from the 128-byte stack buffer -> up to ~8KB kernel stack disclosure (pointers, trapframe data). Privesc not demonstrated: the overwrite content is zeros-only on this table (drm_stats is the only >128B core struct; driver structs <=72B; heap sizes 129..247 contained by the 256B kmalloc bucket).", "evidence": "run.log [1]-[4] (full transcripts: leak hexdump with kernel pointers, 3-run md5 variance, 8191-byte probe, smash console output), panic.txt (Fatal trap 12, instruction pointer = 0x8:0x0, unpriv process 1059), leak_sample.txt, dfioctl_rep.c (replica of drm_ioctl dispatch semantics + drm_getmagic/drm_getstats behaviors), df_primitive.c (unpriv trigger), drm_size_confusion.c (real-DRM trigger for hardware boxes), fix.diff (canonical-cmd check, applied cleanly to guest /usr/src)", "kernel_refs": [ "sys/dev/drm/drm_ioctl.c:801", "sys/dev/drm/drm_ioctl.c:809", "sys/dev/drm/drm_ioctl.c:819-831", "sys/dev/drm/drm_ioctl.c:847", "sys/dev/drm/drm_ioctl.c:854", "sys/dev/drm/drm_ioctl.c:215-224", "sys/dev/drm/drm_ioctl.c:573", "sys/dev/drm/drm_ioctl.c:577", "sys/dev/drm/drm_auth.c:58-76", "sys/kern/sys_generic.c:668-680", "sys/kern/sys_generic.c:729-730", "sys/dev/drm/include/uapi/drm/drm.h:262-268", "sys/dev/drm/drm_drv.c:1178", "sys/dev/drm/include/drm/drmP.h:108-110", "linux v4.7 drivers/gpu/drm/drm_ioctl.c (drv_size/usize/asize + cmd=ioctl->cmd)" ], "poc_changes": "The finding seed had no runnable PoC; everything was authored in this run. Guest build required the kmod.mk route (hand-linked .ko panicked the linker with lost base for relatab); replica module needed DFly-specific dev_ops/dev_ioctl_args fixes (sys/device.h + sys/conf.h, cdev_t). df_primitive adds the VOID|OUT leak mode and the smash mode with hand-encoded BSD ioctl command words.", "attempts": 4, "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": 1800, "guest_dirty": 0, "build_cmd": "make KMOD=dfioctl_rep (via /usr/src/sys/conf/kmod.mk); cc -O2 -Wall -o df_primitive df_primitive.c; cc -O2 -Wall -o drm_size_confusion drm_size_confusion.c", "run_cmd": "kldload ./dfioctl_rep.ko; /tmp/df_primitive leak 2048; /tmp/df_primitive leak 8191; /tmp/df_primitive smash 8", "code_hash": "6bddf09c5ee35b9061205ceec977db1e3f6a2812adf007ac9d124cffb8182fd2", "notes": "Guest has no DRM device (QEMU std VGA chip 0x1111:1234; no /dev/dri; DRM not in X86_64_GENERIC) so the real drm_ioctl() path cannot execute there; replica-module proof per established DF-3715 practice. The over-large IOC_VOID copyout is a sys/kern/sys_generic.c defect (related pass-1 finding DF-2734 covered only the uninit-buffer-to-driver aspect) -- recommend a separate kern finding + fix (bound the IOC_VOID copyout to a pointer word or allocate/bzero like non-VOID). Guest was reset with-src after the deliberate panic; scratch files removed.", "recommended_fix": "In drm_ioctl(), require (unsigned int)cmd == ioctl->cmd (port-side equivalent of upstream cmd=ioctl->cmd) before permit/dispatch; independently bound mapped_ioctl()s copyout for IOC_VOID commands.", "fix_status": "not_testable", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff authored and verified to apply cleanly to the guests byte-identical /usr/src/sys/dev/drm/drm_ioctl.c (patch -p0 RC=0; GNU patch dry-run RC=0). Runtime before/after validation impossible on this guest: no DRM device and DRM is not compiled into X86_64_GENERIC, so the patched dispatch cannot be exercised. Baseline (unpatched) behavior was reproduced via the exact-semantics replica module (leak + RIP=0 panic).", "fix_evidence": "findings/poc/DF-4569/fix.diff; VERDICT.md Fix section; guest apply-check transcript in run.log notes (patch -p0 RC=0, grep -c DF-4569 == 1)" } |