DragonFlyBSD Kernel Audit
DF-2653 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-2653",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "REPRODUCED on stock INVARIANTS kernel #0: a crafted DATA bref with a 16K-aligned key (0x7C000) and radix 16, planted in a file's indirect block covering a hole, is found by the write path for lbase 0x80000 (hammer2_assign_physical, strategy.c:733) and COW-modified; hammer2_chain_modify -> hammer2_freemap_alloc -> hammer2_bmap_alloc computes j=(0x7C000&0x7FFFF)/16384*2=62 with bmradix=8, and the KKASSERT at freemap.c:679 fires: 'panic: assertion \"j + bmradix <= HAMMER2_BMAP_BITS_PER_ELEMENT\" failed in hammer2_bmap_alloc at hammer2_freemap.c:679' with backtrace hammer2_bmap_alloc <- hammer2_freemap_alloc <- hammer2_chain_modify <- hammer2_assign_physical <- hammer2_xop_strategy_write. Panic is async (strategy xop thread). On no-INVARIANTS production kernels the same arithmetic truncates bmmask=(0xFF<<62) to 2 bits: the 64K allocation marks one 16K pair, avail -= 64K, and data_off (element base + 62*8K, 64K long) crosses into the next bitmapq element whose chunks stay 00 -> subsequent allocations overlap (same corruption class as DF-2652). Fixed by turning the KKASSERT into a real gate on the fast path (misaligned sub_key falls back to the general element scan); validated on kernel #1: same forged image + write completes, readback md5 matches, guest stays up.",
 "exploit_chain": "crafted hammer2 image (misaligned-key DATA bref in indirect block) -> mount -> unprivileged write into the covered hole range -> chain found + COW modify -> freemap_alloc -> bmap_alloc j+bmradix=70>64 -> KKASSERT panic (INVARIANTS) / truncated mask + overlapping allocation (noinv)",
 "evidence": [
  "panic.txt: exact assertion text + backtrace through hammer2_xop_strategy_write",
  "run.log iteration 2: MOUNT_OK, WRITE_DONE, then guest down; serial console panic block",
  "run.log fix validation: patched kernel MOUNT_OK, readback md5 f1fdf21f00ddb16c35b1e77fac5eaeac == written data, guest alive",
  "forge_2653.py: poison insertion with ascending-key slot shifting"
 ],
 "kernel_refs": [
  "sys/vfs/hammer2/hammer2_freemap.c:660",
  "sys/vfs/hammer2/hammer2_freemap.c:663",
  "sys/vfs/hammer2/hammer2_freemap.c:676",
  "sys/vfs/hammer2/hammer2_freemap.c:679",
  "sys/vfs/hammer2/hammer2_strategy.c:733",
  "sys/vfs/hammer2/hammer2_chain.c:1388",
  "sys/vfs/hammer2/hammer2_subr.c:267"
 ],
 "poc_changes": "Two iterations: first attempt used CHECK_NONE (0x00) methods on the poison bref and did not panic - hammer2's overwrite-in-place optimization (chain.c:1495-1521) skipped freemap_alloc entirely; fixed by using methods=0x30 (iscsi32) to force the COW realloc. Panic fires asynchronously in the strategy xop, so success must be checked via vm.sh status/log, not the dd exit.",
 "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": 95,
 "guest_dirty": 0,
 "build_cmd": "python3 forge_2653.py base2651.img forge2653.img; python3 (build.sh) sets poison methods=0x30 -> forge2653b.img",
 "run_cmd": "vm.sh run_root 'vnconfig -c vn0 /tmp/forge2653b.img; mount -t hammer2 /dev/vn0@testvol /mnt/h2t; head -c 65536 /dev/urandom > /tmp/w64k3.bin; dd if=/tmp/w64k3.bin of=/mnt/h2t/f2653 bs=64k seek=8 conv=notrunc'",
 "code_hash": "64efbe57e0bdf6649fb630ce836449d92af6140dfc5a8821f939b3d3decc063b",
 "notes": "Kernel-created DATA chains always have radix-aligned keys (calc_logical masks with ~PBUFMASK64), so only crafted images reach the overflow. Related: the general scan loop below (:694-705) is self-bounded and safe.",
 "recommended_fix": "Gate the sub_key fast path with 'if (i >= 0 && j + bmradix <= HAMMER2_BMAP_BITS_PER_ELEMENT)' and fall back to the general element scan otherwise.",
 "fix_status": "fixed",
 "fix_baseline_reproduced": 1,
 "fix_patched_reproduced": 0,
 "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Aug 30 01:17:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_verdict": "fix.diff gates the sub_key fast path with a real (j + bmradix <= 64) check and falls back to the general element scan: on kernel #1 the same forged image + 64K write at f2653+0x80000 completes, readback md5 matches the written bytes, guest stays up; on #0 the same operation panicked at freemap.c:679 in hammer2_xop_strategy_write. Baseline panic gone.",
 "fix_evidence": "fix_run.log (MOUNT_OK, md5 match, guest alive on #1); panic.txt for the #0 baseline assertion + backtrace"
}