DF-2635 / verdict.json
{ "finding_id": "DF-2635", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on the stock INVARIANTS guest kernel. Forged image flips the PFS-root meta.inum from 1 to 0x42 (inode block +0x58). Mount (RW), ls, write, read, sync all succeed -- but the in-memory iroot was hashed under inum=1 at hammer2_inode_get (inode.c:933,954-977) and its meta was then wholesale overwritten from media (vfsops.c:456 pfsalloc, vfsops.c:1975 vfs_root), desyncing it from its hash bucket. umount drops the last iroot ref (vfsops.c:707-711), hammer2_inode_drop computes the bucket from the CURRENT meta.inum (inode.c:617) and the removal walk (inode.c:624-625) runs off the wrong bucket's list: 'Fatal trap 12: page fault while in kernel mode, fault virtual address = 0x0, Stopped at hammer2_inode_drop+0x1e3: movq (%rax),%rdx', current process = umount. Because struct hammer2_inode's first field is 'next', the runaway xipp degenerates to NULL exactly as predicted. Secondary impact: inum-based lookups (nlookupdotdot, vget) cannot find iroot for the mount's lifetime and can spawn duplicate in-memory inodes for the same media chain. Controlled NULL-read panic (DoS) + aliasing from a crafted image; mount requires root.", "exploit_chain": "", "evidence": "panic.txt lines 40-59 (Fatal trap 12, fault VA 0x0, Stopped at hammer2_inode_drop+0x1e3, current process umount), run.log (successful RW mount + 'no recovery needed' then the trap), forge_2635.py, fix.diff, fix_run.log (patched kernel unmounts cleanly)", "kernel_refs": ["sys/vfs/hammer2/hammer2_inode.c:617-631", "sys/vfs/hammer2/hammer2_inode.c:624-625", "sys/vfs/hammer2/hammer2_inode.c:933", "sys/vfs/hammer2/hammer2_inode.c:954-977", "sys/vfs/hammer2/hammer2_vfsops.c:453-459", "sys/vfs/hammer2/hammer2_vfsops.c:1975", "sys/vfs/hammer2/hammer2_vfsops.c:707-711"], "poc_changes": "Cribbed the DF-2616/DF-2624 forger harness; targeted meta.inum at inode block +0x58 of the 'testvol' PFS root; kept meta.type=1 so the mount is fully functional (RW) up to the unmount panic.", "attempts": 1, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026\troot@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "runtime_sec": 120, "guest_dirty": 1, "build_cmd": "python3 forge_2635.py h2base.img craft2635.img (host); vnconfig -c vn1 craft2635.img (guest)", "run_cmd": "mount_hammer2 /dev/vn1@testvol /mnt/h2635 && ls -la /mnt/h2635 && echo hello > /mnt/h2635/f && cat /mnt/h2635/f && sync && umount /mnt/h2635", "code_hash": "74f76d2d222686b0ccf1939337dbc1fcfcba244c7b8bbaa3393438073536ad5b", "notes": "The NULL-read is a panic, not an exploitable corruption (read-only deref of address 0). The aliasing half of the bug (duplicate inodes for one media chain via inum lookups) is real but was not separately weaponized. ssh died at panic; serial capture is the record.", "recommended_fix": "Preserve meta.inum=1 across the two wholesale meta overwrites in hammer2_vfsops.c and harden hammer2_inode_drop's removal walk against falling off the list (see fix.diff).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Aug 29 12:37:23 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff (preserve meta.inum=1 across the two wholesale iroot->meta overwrites in hammer2_vfsops.c + harden hammer2_inode_drop's bucket walk with 'while (*xipp && *xipp != ip)') applied to the guest /usr/src, kernel rebuilt with 'make nativekernel' + installkernel, rebooted into kernel #1. Re-running the exact PoC: forged inum=0x42 image mounts RW, ls/write/cat/sync all work, and 'umount' completes cleanly (UMOUNT_RC=0) where the stock kernel panicked with Fatal trap 12 in hammer2_inode_drop. Guest stayed up (fix_run.log). Baseline panic confirmed on stock kernel #0 in the same session.", "fix_evidence": "fix_build.log (install completed), fix_run.log (MOUNT_RC=0, write+cat 'hello', sync, UMOUNT_RC=0, no panic, vm up)" } |