DF-2650 / verdict.json
{ "finding_id": "DF-2650", "status": "reproduced", "reproduced": 1, "impact": "panic", "confidence": "certain", "verdict": "Reproduced on the stock INVARIANTS kernel #0: a crafted hammer2 image carrying a DATA bref whose data_off radix field is 0 (base 0x1800800, radix bits 0), planted key-sorted into a free blockset slot of the mounted PFS's root inode, survives mount (mount never walks the mounted PFS's root blockset) and, once any local user issues the ungated HAMMER2IOC_BULKFREE_SCAN ioctl (caps_priv_check result ignored, DF-0815), the bulkfree topology scan feeds the bref to h2_bulkfree_callback which trips KKASSERT(radix != 0) at hammer2_bulkfree.c:848 -> kernel panic with full backtrace (h2_bulkfree_callback <- hammer2_bulkfree_scan <- hammer2_bulkfree_pass). Guest down at db> prompt. Non-INVARIANTS kernels compile the assert out and misaccount the record as a 1-byte fragment (marks a 16K block allocated) without memory corruption, so the impact ceiling is a deterministic local DoS on INVARIANTS kernels (plus freemap misaccounting elsewhere).", "exploit_chain": "crafted image (mount privilege) -> PFS root inode blockset slot forged with radix-0 DATA bref (CHECK_NONE ancestors, volhdr CRC32C recomputed) -> mount @testvol succeeds -> unprivileged user opens /mnt/h2x/f1 -> ioctl(HAMMER2IOC_BULKFREE_SCAN) ignores caps_priv_check -> topology scan walks PFS root blockset -> h2_bulkfree_callback -> KKASSERT(radix != 0) panic", "evidence": [ "panic.txt: 'panic: assertion \"radix != 0\" failed in h2_bulkfree_callback at /usr/src/sys/vfs/hammer2/hammer2_bulkfree.c:848' with backtrace h2_bulkfree_callback+0x2a9 <- hammer2_bulkfree_scan.constprop.2 <- hammer2_bulkfree_pass", "run.log: trigger run as uid 65534 on stock kernel #0, guest wedged (ssh timeout), serial console shows the panic", "forge_2650.py: plants DATA bref {type=3, key=0xe000000000000003, data_off=0x1800800 (radix==0)} in blockset slot 2 of the testvol root inode; CHECK_NONE on testvol bref + sroot bref; volhdr CRCs recomputed", "fix_run.log: fixed kernel (fix.diff) prints 'illegal radix-0 data_off' warning and completes the bulkfree (no panic)" ], "kernel_refs": [ "sys/vfs/hammer2/hammer2_bulkfree.c:847-849", "sys/vfs/hammer2/hammer2_bulkfree.c:179", "sys/vfs/hammer2/hammer2_ioctl.c:83", "sys/vfs/hammer2/hammer2_ioctl.c:144-149" ], "poc_changes": "Forger derived from DF-2624 tooling (h2common.py reused verbatim); key fix vs first attempt: the poison bref key must be sorted above the sibling dirent key (hammer2_base_find breaks on the first slot with key >= search key), radix-0 data_off with a valid in-volume base; trigger.c issues HAMMER2IOC_BULKFREE_SCAN (_IOWR('h',92,64B)) directly as an unprivileged user instead of hammer2(8).", "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": 96, "guest_dirty": 0, "build_cmd": "guest: truncate -s 64M base.img && newfs_hammer2 -L testvol base.img && (mount, create f1, sync, umount); host: python3 forge_2650.py base2650.img craft2650.img rad0; guest: cc -O -o trigger trigger.c", "run_cmd": "vnconfig -c vn0 /tmp/craft2650.img && mount -t hammer2 /dev/vn0@testvol /mnt/h2x && su -m nobody -c '/tmp/trigger /mnt/h2x/f1'", "code_hash": "323b5e2fdc23e610313b34d03ab4aec2e350a7cec755cba3b453db9dc12aabf9", "notes": "panic impact on INVARIANTS kernels only for the assert itself; on production kernels the same forged bref causes freemap misaccounting (one 16K block marked allocated with avail -= 16384 per encounter). Distinct from DF-2616 (io window overrun), DF-2617 (zero-offset data_off with error==0) and DF-0818 (div-by-zero).", "recommended_fix": "Replace KKASSERT(radix != 0) in h2_bulkfree_callback with a kprintf + return 0 (skip the record), as in fix.diff.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Sun Aug 30 00:18:56 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (both bulkfree.c fixes applied)", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Baseline stock INVARIANTS kernel #0: panic \"assertion radix != 0 failed in h2_bulkfree_callback at hammer2_bulkfree.c:848\", guest down. Fixed kernel (make nativekernel + installkernel, -Werror clean): same crafted image mounts, same unprivileged ioctl completes with r=0, console shows the new \"illegal radix-0 data_off 0000000001800800 e000000000000003/64\" warning and the full scan finishes (100.00% storage processed). Bad behavior gone.", "fix_evidence": [ "fix_build.log (build excerpts + completion)", "fix_run.log (patched-kernel run: warning + clean completion, no panic)" ] } |