DF-5807 / verdict.json
{ "finding_id": "DF-5807", "status": "reproduced", "reproduced": true, "impact": "panic", "confidence": "certain", "verdict": "vop_write_dirent() (sys/kern/vfs_subr.c:2575) bcopy()s d_namlen bytes from the callers source with no source bound; the UFS OFSFMT readdir path validates dp->d_namlen (the old-format namlen MSB) but passes dp->d_type (the real LSB namlen, up to 255) to the helper. A crafted old-format FFS image (fs_maxsymlinklen=0) with a final 8-byte dirent claiming namlen 0x00FF makes getdents(2) copy 255 bytes starting exactly at the end of the buffer-cache buffer. Reproduced 2/2 deterministically as a kernel page fault in memcpy on the stock kernel; with adjacent KVM mapped the same primitive is a per-record kernel-heap disclosure to unprivileged readers of a mounted crafted image. Fixed kernel (fix.diff applied in-guest, quickkernel+installkernel+reboot) rejects the record with EIO - no panic.", "exploit_chain": "craft UFS1 image (patch fs_maxsymlinklen=0; root dir block last record namlen16=0x00FF, reclen=8) -> root mounts image (crafted-removable-media / usermount threat model) -> unprivileged getdirentries() -> vop_write_dirent bcopy reads 255 bytes past bp->b_data+bufsize -> OOB read: panic when neighbor page unmapped (demonstrated), else heap disclosure in dirent name field", "evidence": "panic.txt/panic2.txt (Fatal trap 12, Stopped at memcpy+0x19, fault addr == first qword past the buffer, two fresh-snapshot runs); craft.c/getd.c (rig); run_fixed.log (patched kernel: n=24, SURVIVED-NOPANIC); fix.diff (validated)", "kernel_refs": [ "sys/kern/vfs_subr.c:2560-2582", "sys/vfs/ufs/ufs_vnops.c:1661-1676", "sys/vfs/ufs/ufs_vnops.c:117", "sys/vfs/fuse/fuse_vnops.c:1071-1086" ], "poc_changes": "seed sketch rewritten entirely: crafted-image rig (UFS1 superblock+inode+dirent patcher using guest kernel headers) plus getdirentries dumper; fixed a reclen offset bug in the initial crafter (bsize-6 -> bsize-4)", "attempts": 4, "guest_uname": "DragonFly dfbsd 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": 1500, "guest_dirty": 0, "build_cmd": "cc -O2 -I/usr/src/sys -I/usr/src/sys/vfs/ufs -o /tmp/craft craft.c && cc -O2 -D__BSD_VISIBLE=1 -o /tmp/getd getd.c", "run_cmd": "(root) craft /root/f1.img; vnconfig+mount -t ufs -o ro /dev/vn2 /mnt/f1; (user) /tmp/getd /mnt/f1", "code_hash": "see SHA256SUMS in this directory", "notes": "mount precondition (root mounts crafted image, or vfs.usermount=1 removable-media path); unprivileged trigger post-mount. fuse_readdir has the same missing source bound (freclen underflow) - hardening recommended in vop_write_dirent (cap d_namlen) plus fuse-side length check.", "recommended_fix": "ufs_vnops.c: validate the namlen actually passed (on_disk_namlen = OFSFMT ? dp->d_type : dp->d_namlen) against d_reclen before vop_write_dirent; see fix.diff (validated on guest)", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "baseline: 2/2 deterministic Fatal trap 12 in memcpy on stock kernel; after applying fix.diff, rebuilding and rebooting (kernel #1 01:22:48), the identical PoC returns n=24 with the crafted record rejected (EIO) and the guest stays up (run_fixed.log)", "fix_evidence": "run_fixed.log; fix.diff; VERDICT.md" } |