DF-3095 / verdict.json
{ "finding_id": "DF-3095", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "REPRODUCED on the stock INVARIANTS guest. hammer_vfs_mount() loads vol0_next_tid into hmp->next_tid without validation (hammer_vfsops.c:731); hammer_alloc_tid() (hammer_transaction.c:162-181) has no wrap handling and can only hpanic. Variant 1 (next_tid=0xFFFFFFFFFF000000): RW mount succeeds, the first ordinary file creation panics the kernel (panic: hammer_alloc_tid: Ran out of TIDs!, backtrace hammer_alloc_objid<-hammer_create_inode<-hammer_vop_ncreate; panic.txt). Variant 2 (next_tid=0xFFFFFFFFFFFFFFFF): no panic, no error - with the default master_id=0 the 16-alignment add wraps the TID counter to 0 and post-wrap records carry create_tid ~0x8010 on a filesystem whose TID space starts at 0x100000000; demonstrated snapshot-isolation break by mounting -T <synctid taken before file b existed> and observing b inside the historical view (run.log). Fix validated: patched kernel rejects both variants at mount (ERANGE mount: Result too large) and a stock image mounts/operates/umounts normally (fix_run.log).", "exploit_chain": "crafted HAMMER1 media (8-byte header patch, no CRC fixup needed per DF-3042) -> privileged mount_hammer RW succeeds cleanly -> any user with write access to any directory on the mount creates a file -> [panic variant] kernel panic Ran out of TIDs! (deterministic DoS); [wrap variant] silent TID-space wrap -> new records visible in every snapshot/asof view of the filesystem (temporal isolation break).", "evidence": [ "findings/poc/DF-3095/panic.txt - console panic + backtrace (panic variant)", "findings/poc/DF-3095/run.log - both variants + the -T asof snapshot-leak proof (ls shows b inside the pre-b snapshot)", "findings/poc/DF-3095/fix_run.log - patched kernel: both mounts rejected ERANGE, stock-image regression clean", "findings/poc/DF-3095/fix.diff - mount-time validation of vol0_next_tid" ], "kernel_refs": [ "sys/vfs/hammer/hammer_vfsops.c:731", "sys/vfs/hammer/hammer_transaction.c:162", "sys/vfs/hammer/hammer_transaction.c:180", "sys/vfs/hammer/hammer_disk.h:784" ], "poc_changes": "Wrote tid_craft.c fresh (no prior seed): 8-byte patch of vol0_next_tid at header offset 0xF8 (248, verified by offsetof against hammer_disk.h); no CRC fixup required. Discovered during verification that mount_hammers as-of option is -T (not -o asof=) and that hammer synctid <fs> prints a bare 0xTID line; also that default mounts use master_id=0, so the wrap goes through the (next+16)&~15 branch.", "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 (baseline panic); fix kernel #1 Sun Sep 6 07:33:35 UTC 2026", "runtime_sec": 2520, "guest_dirty": 0, "build_cmd": "cc -O -o tid_craft tid_craft.c -I/usr/src/sys/vfs/hammer && truncate -s 4G base.img && vnconfig -c vn0 base.img && newfs_hammer -f -L ua3095 /dev/vn0 && vnconfig -u vn0", "run_cmd": "./tid_craft tid.img panic|wrap; vnconfig -c vn0 tid.img; mount_hammer /dev/vn0 /mnt; echo hello > /mnt/x (panic) | wrap + hammer history + mount_hammer -T <old synctid>", "code_hash": "8fe1c975318476cd670ba3b2caa51cd07c6becc75b79e4c856bc5e226e582af2", "notes": "Secondary impact of the wrap variant beyond DoS: silent break of HAMMER temporal semantics - post-wrap records (create_tid~0) are visible in every snapshot and sort before all real records, corrupting mirror/prune ordering assumptions. The panic variant is triggerable by ANY user after the privileged mount; the mount itself reports success. Related family: DF-3042 (vol_crc never verified - enables header crafting), DF-3040/3067/3068 (other unvalidated header fields).", "recommended_fix": "Validate vol0_next_tid at mount: reject vol0_next_tid >= 0xFFFFFFFFFF000000ULL with ERANGE (fix.diff, validated on the guest)", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep 6 07:33:35 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 guest /usr/src, make nativekernel + installkernel, reboot into #1: panic variant -> mount rejected (ERANGE), guest up; wrap variant -> mount rejected; stock newfs_hammer image -> mount RW, root+user writes, sync, umount all clean.", "fix_evidence": "findings/poc/DF-3095/fix_run.log" } |