DF-3149 / verdict.json
{ "finding_id": "DF-3149", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "One HAMMERIOC_MIRROR_WRITE with two mrecs (REC creating a well-formed record create_tid=T delete_tid=0; PASS with same base and delete_tid=T) panics the stock INVARIANTS kernel at hammer_delete_at_cursor() KKASSERT(delete_tid != create_tid) (hammer_object.c:2501-2502). hammer_mirror_check (mirror.c:777-786) approves any wire delete_tid != 0 differing from the on-disk one and hammer_mirror_update (mirror.c:814-833) passes it unvalidated; the ADJUST path stores the tid first, then asserts. Production (non-INVARIANTS) kernels store delete_tid==create_tid - the duplicate-record B-Tree invariant violation hammer_object.c:2496-2502 explicitly warns about. Needs no crafted creation: any existing slave record whose create_tid the master knows is killable with a single PASS mrec. fix.diff (reject delete_tid <= cursor leaf create_tid) validated: record 1 still created (count=80), mrec 2 rejected EINVAL, no panic.", "exploit_chain": "gated ioctl or malicious mirror-master stream -> HAMMERIOC_MIRROR_WRITE -> write_rec/write_pass -> hammer_mirror_check approves -> hammer_mirror_update -> hammer_delete_at_cursor(ADJUST, wire delete_tid) -> stores delete_tid==create_tid (hammer_object.c:2468) -> KKASSERT (hammer_object.c:2501-2502) panic", "evidence": [ "panic.txt: console panic + backtrace hammer_delete_at_cursor<-hammer_ioc_mirror_write", "run.log: baseline two-mrec trigger (ssh dies, guest down)", "run.fixed.log: patched kernel creates mrec1 (count=80) then rejects mrec2 with error 22, no panic", "fix.diff: validated kernel fix" ], "kernel_refs": [ "sys/vfs/hammer/hammer_mirror.c:777", "sys/vfs/hammer/hammer_mirror.c:814", "sys/vfs/hammer/hammer_mirror.c:828", "sys/vfs/hammer/hammer_mirror.c:625", "sys/vfs/hammer/hammer_mirror.c:696", "sys/vfs/hammer/hammer_object.c:2459", "sys/vfs/hammer/hammer_object.c:2468", "sys/vfs/hammer/hammer_object.c:2501" ], "poc_changes": "Same harness as DF-3148; duptid mode sends REC(btype=R) + PASS(delete_tid==create_tid) in one 160-byte ubuf", "attempts": 2, "guest_uname": "DragonFly dfbsd 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": 70, "guest_dirty": 0, "build_cmd": "cc -O -o /tmp/mirror_poc mirror_poc.c", "run_cmd": "/tmp/mirror_poc /mnt/f1 duptid (fresh 2GB newfs_hammer on vn0 mounted at /mnt)", "code_hash": "4b94e084cfbf563d1409e503e025609a43f52a9bd48992fdf68e1e296f2ba3a6", "notes": "Guest panic line is hammer_object.c:2502 (assert spans 2501-2502 in audited tree). Non-INVARIANTS impact is the stored duplicate-record state, not a panic.", "recommended_fix": "In hammer_mirror_update reject mrec->leaf.base.delete_tid <= cursor->leaf->base.create_tid (see fix.diff).", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Mon Sep 7 05:10: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": "Same patched build as DF-3148 (fix.diff includes the update-path guard): identical PoC on fresh fs creates mrec1s record (count=80) and aborts with head.error=22 on mrec2; no panic; guest up.", "fix_evidence": "run.fixed.log; patched-kernel uname #1 (2026-09-07)" } |