DF-3040 / verdict.json
{ "finding_id": "DF-3040", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED twice on the stock INVARIANTS kernel (fresh vm.sh reset with-src between runs): mounting a newfs_hammer image whose vol0_blockmap[3] undo map was patched to zone3 short offset 0x400000000 (idx 2048, bp+17288) resp. 0x4000000000 (idx 32768, bp+263048) panics in hammer_undo_lookup+0x84 โ the faulting instruction 'addq 0x388(%rcx,%rdx,8),%rbx' is exactly the unbounded vol0_undo_array[HAMMER_UNDO_INDEX(zone3)] load (0x388=904=offsetof), reading past the 16KB rootvol bp at attacker-chosen distance (up to ~4GB reach, idx<2^29). The only undo-path bounds (hammer_recover.c:232 first/next vs alloc_offset; hammer_undo.c:70 KKASSERT vs the same alloc_offset) are all derived from the attacker-crafted vol0_blockmap[3], so none constrains the index against HAMMER_MAX_UNDO_BIGBLOCKS=128 (constant unreferenced in sys/). Impact ceiling: deterministic mount-time kernel panic on INVARIANTS (guest wedged at db> both runs); on non-INVARIANTS kernels an attacker-aimed OOB kernel read whose value steers a zone-2 offset (no userspace disclosure of the OOB bytes demonstrated; the in-bounds idx 128..1792 variant additionally yields a fully attacker-known zone-2 offset from the attacker's own image bytes past the header struct). Fixed and validated: fix.diff bounds zone3_off to the format's 128-bigblock undo range in hammer_undo_lookup (EIO); on the rebuilt kernel #1 the identical PoC fails the mount cleanly with I/O error and the guest stays up; stock-image mount/create/rm/sync/umount regression passes.", "exploit_chain": "", "evidence": [ "panic_idx32768.txt: 'Stopped at hammer_undo_lookup+0x84: addq 0x388(%rcx,%rdx,8),%rbx' with fault VA 0xfffff800641f6380 (page not present) โ the vol0_undo_array[idx] load faulted 240KB past the 16KB bp", "panic_idx2048.txt: same instruction, fault VA 0xfffff80060cea380 for idx 2048 (bp+17288) โ distance tracks the crafted index exactly", "run.log: craft output 'undo AFTER first=3000004000000000 โฆ idx(first)=32768 -> read at bp+263048' followed by the trap; both baseline runs wedged the guest (ssh timeout 124, vm.sh status down)", "fix_run.log (kernel #1 + fix.diff): same PoC -> 'mount: Input/output error', console 'Unable to read UNDO TAIL at 3000003ffffffff8 / recovery failure during seqno backscan / Failed to recover HAMMER filesystem on mount', guest stays up", "fix_build.log: make nativekernel KERNCONF=X86_64_GENERIC completed; uname DragonFly 6.5-DEVELOPMENT #1 Sat Sep 5 16:07:13 UTC 2026", "run.log tail: freemap_probe on stock newfs image clean โ DF-3011's blockmap.c:832 layer2 artifact is NOT a stock-kernel bug (deepforge grafting artifact)" ], "kernel_refs": [ "sys/vfs/hammer/hammer_ondisk.c:688-689", "sys/vfs/hammer/hammer_ondisk.c:703", "sys/vfs/hammer/hammer_undo.c:58-76", "sys/vfs/hammer/hammer_disk.h:512", "sys/vfs/hammer/hammer_disk.h:789", "sys/vfs/hammer/hammer_disk.h:824-829", "sys/vfs/hammer/hammer_recover.c:232", "sys/vfs/hammer/hammer_recover.c:257-263", "sys/vfs/hammer/hammer_recover.c:798", "sys/vfs/hammer/hammer_vfsops.c:671" ], "poc_changes": "Pass-2 author wrote the PoC from scratch (no seed): undo_craft.c patches only vol0_blockmap[3] of a real newfs_hammer image (no CRC fixup needed โ vol_crc is never verified, see DF-3042). One iteration: the first constant 0x400000000 is 2^34 (idx 2048), kept as default because it already faults 904B past the bp; the parametrized run at 0x4000000000 (idx 32768) demonstrates the far reach. Fixed an argv-check bug found during run 2.", "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": 3240, "guest_dirty": 1, "build_cmd": "sh build.sh (cc -O -o undo_craft undo_craft.c -I/usr/src/sys/vfs/hammer; truncate 12G + vnconfig + newfs_hammer -L uabase + vnconfig -u)", "run_cmd": "sh run.sh 0x4000000000", "code_hash": "d69bc7a3943abffd117d4cb53df26cf8e46f415661747d9d6a7775c6a6f889a1", "notes": "Memory-corruption class: impact honestly classified as panic (deterministic DoS from crafted image) โ the OOB read value is not returned to userspace; on non-INVARIANTS kernels the primitive is an attacker-aimed kernel read (CWE-125) with idx up to 2^29. Mount of crafted image needs root (or vfs.usermount=1), same precondition family as DF-0797/DF-0798. Priority-7 freemap off-by-8 investigated and refuted (freemap_probe.sh clean on stock and fixed kernels). DF-0776's proposed count/type validation is still NOT in-tree (only KKASSERTs in hammer_btree.c); DF-0797/DF-0798 also remain unfixed in-tree.", "recommended_fix": "In hammer_undo_lookup(), reject zone3_off >= HAMMER_ENCODE_UNDO(HAMMER_MAX_UNDO_BIGBLOCKS * HAMMER_BIGBLOCK_SIZE64) with EIO before calling hammer_xlate_to_undo() (see fix.diff)", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Sat Sep 5 16:07:13 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 applied to the guest's /usr/src copy only (audit tree untouched; git apply --check clean against sys/), make nativekernel KERNCONF=X86_64_GENERIC + installkernel, reboot into kernel #1. Identical PoC (run.sh 0x4000000000) now fails cleanly: 'mount: Input/output error' via EIO from hammer_undo_lookup -> 'recovery failure during seqno backscan' -> 'Failed to recover HAMMER filesystem on mount'; no panic, guest stays up. Regression on fixed kernel: stock newfs_hammer image mount + file create/rm/sync/umount cycle clean.", "fix_evidence": [ "fix_run.log: mount: Input/output error (SSH_RC=1, guest up)", "run.log console section (fixed kernel): HAMMER(uabase) Unable to read UNDO TAIL at 3000003ffffffff8 / recovery failure during seqno backscan / Failed to recover HAMMER filesystem on mount", "fix_build.log: kernel build + install completed; uname #1 Sat Sep 5 16:07:13 UTC 2026" ] } |