DF-3037 / verdict.json
{ "finding_id": "DF-3037", "status": "reproduced", "reproduced": 1, "impact": "dos", "confidence": "certain", "verdict": "msdosfs_rename() calls doscheckpath() (vnops.c:1045) whose ancestor walk (lookup.c:822-863) has no iteration bound; on a crafted FAT image whose rename-destination '..' chain cycles without passing through the root or source cluster, the walk never terminates (after the first pass every bread() is a cache hit and every deget() a hash hit, so it never sleeps). Verified on the stock INVARIANTS guest: control rename returns in 0s; with a two-directory B<->D '..' cycle installed, rename(2) never returned, kill -9 was never delivered, and the whole 6-CPU guest stopped servicing ssh (banner-exchange timeouts) with no panic on serial \u2014 a system-wide livelock requiring reboot. Bound-the-walk fix.diff validated: patched kernel #1 returns EINVAL for the cycle rename in <1s and the guest stays fully responsive; control rename unaffected. Current FreeBSD has the same unbounded loop, so this is an upstream-live hardening gap.", "exploit_chain": "craft FAT16 image (16MB, newfs_msdos) with dirs A/A/moveme/B/D -> patch the 16-bit deStartCluster of B's and D's '..' entries to point at each other (dirpatch) -> root mounts rw (vnconfig+mount_msdos; or user with vfs.usermount=1 owning the image) -> any user with write access runs rename(/mnt/A/moveme, /mnt/B/m2) -> kernel spins forever in doscheckpath: unkillable thread + token-churn livelock -> guest-wide DoS until reboot", "evidence": [ "run.log: control rename 'rename returned success ... elapsed=0s'; after cycle patch 'TRIGGER STILL RUNNING AFTER 15s', 'KILL -9 INEFFECTIVE (stuck in kernel) -- DoS CONFIRMED'", "run.2.log: post-trigger ssh probe exits 255 'Connection timed out during banner exchange' (guest no longer services logins); boot.log serial console shows no panic", "run.fixed.log: patched kernel - 'rename: Invalid argument' in <1s, process exits, guest responsive afterwards", "fix.diff: depth cap 4096 in doscheckpath, applies cleanly (patch --dry-run verified) and builds (nativekernel BUILD_RC=0)" ], "kernel_refs": [ "sys/vfs/msdosfs/msdosfs_vnops.c:1045", "sys/vfs/msdosfs/msdosfs_lookup.c:822", "sys/vfs/msdosfs/msdosfs_lookup.c:861" ], "poc_changes": "none vs seed (pack authored fresh in this run; no seed existed)", "attempts": 2, "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 (baseline) / see fix_kernel_uname", "runtime_sec": 1150, "guest_dirty": 0, "build_cmd": "cc -O -Wall -o /root/df3037/dirpatch /root/df3037/dirpatch.c && cc -O -Wall -o /root/df3037/trigger /root/df3037/trigger.c", "run_cmd": "sh /root/df3037/run.sh", "code_hash": "9d8e9788efecd70b38f9cba0738c2f4ee1ebeadd95ae6fb8c4565d4f6a6be5a7 (trigger.c); 6edeed7ef7e1d8c1fedd7ce19ff8b6f68aed974001f78306ddf1d0f898b8db3c (dirpatch.c); 1ea3498350ec3a1ea516d6bd556bab205b4ab5b4b52ccd1adf3254a05865b7ea (run.sh); 2def247f9cbd436b6ae4950cef6429b411d61629d13e499fb62c4ffc98b5adb7 (fix.diff)", "notes": "rename(2) is the only caller of doscheckpath; requires rw mount of the crafted image (root, or vfs.usermount=1 owner). Upstream FreeBSD sys/fs/msdosfs/msdosfs_lookup.c doscheckpath has the identical unbounded for(;;) (checked 2026-09). Baseline wedge dirtied the guest; guest was reset (vm.sh reset with-src) before and after fix validation.", "recommended_fix": "Bound the doscheckpath '..' ancestor walk (depth cap 4096 -> EINVAL); see fix.diff.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Sep 5 15:02:31 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Rebuilt X86_64_GENERIC with fix.diff (doscheckpath depth cap 4096 -> EINVAL; patch -p1 applied cleanly, make nativekernel BUILD_RC=0, installkernel INSTALL_RC=0, kernel #1 booted). Baseline kernel #0 (same PoC, run.log): cycle-rename hangs forever, kill -9 ineffective, guest-wide ssh outage. Patched kernel #1 (run.fixed.log): identical crafted image + identical rename returns 'rename: Invalid argument' (EINVAL) in <1s, trigger exits rc=1, guest fully responsive (uptime/load normal, run.sh completes RUN_RC=0); control rename on unpatched image still succeeds in 0s. Bad behavior gone.", "fix_evidence": "run.fixed.log (control OK + cycle rename EINVAL + responsive guest); fix_build.log (build/install completion tails, BUILD_RC=0/INSTALL_RC=0); fix.diff applies cleanly (patch -p1 verified in-guest and out-of-guest)" } |