DF-5841 / verdict.json
{ "finding_id": "DF-5841", "status": "not_reproduced", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Code defect certain, primitive reachability-gated: mapped_ioctl() sizes the ioctl scratch buffer with `(com & IOC_VOID) == 0 && size > sizeof(ubuf.stkbuf)` (sys_generic.c:674) but copies back `size` bytes for any IOC_OUT command on success (:729-730), so an unprivileged IOC_VOID|IOC_OUT command with IOCPARM_LEN in (128,8191] reads up to 8063 bytes past the 128-byte on-stack ubuf.stkbuf into userland. The copyout only runs when the underlying fo_ioctl() returns 0, and a 58,496-command sweep across 11 unprivileged fd types (/dev/null /dev/zero /dev/urandom /dev/tty VREG VDIR pipe rd/wr udp tcp unix kqueue; groups 0-255, nums 0/127, dir VOID|OUT and VOID|IN|OUT) produced zero successes on the default guest config (kernel+ehci+xhci) - every reachable fo_ioctl dispatches on exact constants and returns ENOTTY/EINVAL. Filed as Info hardening: one future permissive driver turns this into an ~8KB kernel-stack disclosure. Fix is a one-condition allocation change (see fix.diff).", "exploit_chain": "", "evidence": "run.log: fd types: 11 ... done, hits=0 (58,496 crafted ioctls, 0 returned 0). VERDICT.md: primitive proof at sys_generic.c:674-682 vs :729-730; per-driver exact-constant dispatch citations (vfs_vnops.c:983, sys_pipe.c:993, sys_socket.c:140+, kern_event.c:1660, tty.c:853).", "kernel_refs": [ "sys/kern/sys_generic.c:668", "sys/kern/sys_generic.c:674", "sys/kern/sys_generic.c:681", "sys/kern/sys_generic.c:729", "sys/sys/ioccom.h:38", "sys/kern/vfs_vnops.c:983", "sys/kern/sys_pipe.c:993", "sys/kern/sys_socket.c:180", "sys/kern/kern_event.c:1660" ], "poc_changes": "Wrote scan rig from scratch (no seed): sweeps fd types x cmd groups x numbers x direction combos at len=291, flags buffer markers, reports any rc==0 with hexdump.", "attempts": 1, "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": 12, "guest_dirty": 0, "build_cmd": "cc -O -o /tmp/ioctl_scan /tmp/ioctl_scan.c", "run_cmd": "/tmp/ioctl_scan; echo EXIT=$?", "code_hash": "872acfaf727ac909bc01c5856ea53309a8def68fbd7beb06b5c81186cf8ae26d", "notes": "Small-size (<=128) VOID|OUT commands additionally leak the uninitialized remainder of ubuf.stkbuf because the OUT bzero at :691-696 is short-circuited by the VOID branch - adjacent to DF-2734s class, covered by the same fix.diff bzero. Guest modules: kernel+ehci.ko+xhci.ko only; the only masked-command dispatchers in-tree (evdev IOCBASECMD cdev.c:514, sound IOCGROUP dsp.c:1112) are absent, and IOCBASECMD preserves direction bits anyway.", "recommended_fix": "Size the ioctl scratch buffer by `size` regardless of IOC_VOID (kmalloc with M_ZERO when size>128) and bzero the stack buffer for IOC_VOID|IOC_OUT commands so copyout never reads past 128 initialized bytes", "fix_status": "not_tested", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff not kernel-rebuilt: the finding is Info/hardening and did not reproduce on the default configuration (no permissive fo_ioctl to unlock the overread), so there is no observable baseline behavior to validate a patched kernel against. The diff removes the primitive structurally (allocation no longer skips on IOC_VOID; heap zeroed; stack zeroed for VOID|OUT).", "fix_evidence": "fix.diff (3 hunks against sys/kern/sys_generic.c, generated against the read-only tree, never applied)." } |