DF-3101 / verdict.json
{ "finding_id": "DF-3101", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED on the stock INVARIANTS kernel #0: an unprivileged ftruncate(2) on a crafted HPFS image (fnode with AB_NODES root alblk pointing at a 5000-sector AlSec chain, each an_nextoff=0xFFFFFFFF) drives hpfs_truncatealblk into unbounded recursion until the kernel stack guard page double-faults: console shows DOUBLE FAULT - KERNEL STACK GUARD HIT! / panic: double fault, rsp exactly on the guard. Root cause: hpfs_alsubr.c:760 loop condition (bn <= an_nextoff) is always true for bn=0, :770-775 recursion has no depth limit/visited-set; entry hpfs_vnops.c:559-569 -> hpfs_subr.c:774. Deterministic DoS/panic under the crafted-image threat model (RW mount required - stock mount_hpfs(8) forces MNT_RDONLY, so a direct mount(2) helper is needed, DF-0859 precedent). Fix (depth limit 32) validated on a rebuilt kernel: same trigger returns EINVAL, no panic.", "exploit_chain": "attacker supplies crafted HPFS image -> root mounts RW (mount(2) helper; mountpoint uid handed to user) -> unprivileged user opens FILE O_WRONLY (owner) -> ftruncate(fd,0) -> hpfs_setattr (vnops.c:559) -> hpfs_truncate (subr.c:774) -> hpfs_truncatealblk recursion 5000 deep (alsubr.c:770-775) -> kernel stack guard page -> double fault -> panic. No memory corruption escapes the guard page; impact ceiling is denial of service.", "evidence": [ "panic.txt: DOUBLE FAULT - KERNEL STACK GUARD HIT! / panic: double fault / rsp=0xfffff801182dd000 on the guard page", "run.3.log: RW mount of df3101.img, unprivileged ftruncate trigger, RUN_EXIT=124 (syscall never returned), guest down", "craft_img.py: 5000-sector AB_NODES AlSec chain (an_nextoff=0xFFFFFFFF each) terminating in an empty leaf", "fix_run.log: patched kernel (fix.diff, depth limit) - same trigger returns EINVAL, no panic, guest stays up" ], "kernel_refs": [ "sys/vfs/hpfs/hpfs_alsubr.c:742", "sys/vfs/hpfs/hpfs_alsubr.c:760", "sys/vfs/hpfs/hpfs_alsubr.c:764", "sys/vfs/hpfs/hpfs_alsubr.c:770-775", "sys/vfs/hpfs/hpfs_subr.c:774", "sys/vfs/hpfs/hpfs_vnops.c:559-569" ], "poc_changes": "Image crafter + RW-mount helper built from scratch on the verified DF-0857/DF-0859 pack skeleton; trigger is a 3-line ftruncate program. No seed PoC existed.", "attempts": 3, "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": 145, "guest_dirty": 1, "build_cmd": "python3 craft_img.py df3101.img; cc -O2 -Wall -o /tmp/df3101_poc /tmp/df3101_poc.c; cc -O2 -I/usr/src/sys -o /root/mount_rw /root/mount_rw.c", "run_cmd": "sh /tmp/df3101_setup.sh; (as uid 1001) /tmp/df3101_run /tmp/hpfsmp/FILE", "code_hash": "47b0776879f4634830f8be1287be5254ea900b407ab091b69454e254f7ddc395", "notes": "Cyclic variant of the same chain instead self-deadlocks on the per-level held buffer lock (unkillable hang) - noted in VERDICT.md, not separately verified. Distinct from DF-0858 (hpfs_hpbmap goto-loop, no stack growth).", "recommended_fix": "Depth-limit hpfs_truncatealblk recursion (depth argument, HPFS_MAXALSECDEPTH=32, EINVAL past it); see fix.diff.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep 6 09:09:15 UTC 2026 (fix.diff applied, make nativekernel, X86_64_GENERIC)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline (unpatched #0): double-fault panic. Patched (fix.diff, make nativekernel, reboot): identical unprivileged trigger returns EINVAL, no panic, guest stays up.", "fix_evidence": "fix_run.log" } |