DF-3148 / verdict.json
{ "finding_id": "DF-3148", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Single HAMMERIOC_MIRROR_WRITE ioctl with one REC mrec whose wire leaf.base.btype==0 (data_len=0) panics the stock INVARIANTS kernel at hammer_btree_insert() KKASSERT(elm->base.btype != 0) (hammer_btree.c:816) via hammer_create_at_cursor (the wire leaf is stored verbatim; only data_len is validated at hammer_mirror.c:575-579). Nonzero-but-wrong btype is stored and panics later at hammer_mirror.c:737 / hammer_object.c:2448 on delete-through. Malicious mirror master -> slave kernel DoS; local root can trigger directly. Not DF-0867 (that is rec_type->hammer_alloc_data hpanic on the data_len>0 path). fix.diff (reject btype != HAMMER_BTREE_TYPE_RECORD in write_rec) validated on a rebuilt kernel: no panic, stream rejected EINVAL.", "exploit_chain": "gated ioctl (SYSCAP_NOVFS_IOCTL) or malicious mirror-master stream -> HAMMERIOC_MIRROR_WRITE -> hammer_ioc_mirror_write_rec (only data_len validated) -> hammer_create_at_cursor (data_len==0 skips all alloc/CRC checks) -> hammer_btree_insert -> KKASSERT(btype != 0) panic", "evidence": [ "panic.txt: exact console panic + backtrace hammer_btree_insert<-hammer_create_at_cursor<-hammer_ioc_mirror_write", "run.log: baseline trigger (ssh dies, guest down)", "run.fixed.log: patched kernel rejects with error 22, no panic", "fix.diff: validated kernel fix" ], "kernel_refs": [ "sys/vfs/hammer/hammer_mirror.c:575", "sys/vfs/hammer/hammer_mirror.c:429", "sys/vfs/hammer/hammer_object.c:2274", "sys/vfs/hammer/hammer_object.c:2298", "sys/vfs/hammer/hammer_btree.c:816", "sys/vfs/hammer/hammer_mirror.c:737", "sys/vfs/hammer/hammer_object.c:2448" ], "poc_changes": "Hand-rolled wire structs (hammer_ioc_mirror_rw=200B, mrecord_rec=80B, base_elm=40B with btype at +0x23) with _Static_asserts; no kernel headers needed; data_len=0 avoids the DF-0867 rec_type hpanic so the btype assert is the only sink", "attempts": 3, "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": 95, "guest_dirty": 0, "build_cmd": "cc -O -o /tmp/mirror_poc mirror_poc.c", "run_cmd": "/tmp/mirror_poc /mnt/f1 btype0 (fresh 2GB newfs_hammer on vn0 mounted at /mnt)", "code_hash": "4b94e084cfbf563d1409e503e025609a43f52a9bd48992fdf68e1e296f2ba3a6", "notes": "First btype0 run did not panic because the earlier nodata run had already created the identical key (lookup found it, mirror_check okd delete_tid 0==0); re-ran on a fresh filesystem -> panic. Guest kernel config X86_64_GENERIC line 56 options INVARIANTS.", "recommended_fix": "In hammer_ioc_mirror_write_rec reject mrec->leaf.base.btype != HAMMER_BTREE_TYPE_RECORD (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": "make -j6 nativekernel RC=0 + make installkernel RC=0 with fix.diff; identical PoC on fresh fs: head.error=22 (EINVAL), count=0, no panic, guest stayed up. Baseline panic gone.", "fix_evidence": "run.fixed.log; patched-kernel uname #1 (2026-09-07)" } |