DragonFlyBSD Kernel Audit
DF-2651 / verdict.json
← back to finding ↓ download raw
{
 "finding_id": "DF-2651",
 "status": "reproduced",
 "reproduced": 1,
 "impact": "panic",
 "confidence": "certain",
 "verdict": "REPRODUCED on stock INVARIANTS kernel #0: mounting a crafted hammer2 image whose volume header freemap_blockset is blanked (no FREEMAP_LEAF for any 1GB region) and whose sroot/testvol brefs carry mirror_tid > freemap_tid makes the mount-time recovery scan call hammer2_freemap_adjust(DORECOVER) (vfsops.c:2234); the freemap lookup returns NULL, the guard at freemap.c:1016 is structurally dead (how is KKASSERTed == DORECOVER at :972), and the unconditional 'if (chain->error)' at freemap.c:1021 reads NULL+0x170 -> Fatal trap 12, guest down at db>. INVARIANTS-independent (no assertion involved). Ceiling is panic/DoS from crafted-image mount (page 0 unmapped on amd64); not escalatable to code execution. Fixed by reordering the NULL handling so DORECOVER falls through to the chain_create at :1037; validated on rebuilt kernel #1: same image mounts cleanly, files usable, guest stays up.",
 "exploit_chain": "crafted hammer2 image (blanked freemap_blockset + bumped mirror_tids + recomputed volhdr CRC32C) -> victim mounts (root / vfs.usermount=1 / automounter) -> hammer2_recovery_scan -> hammer2_freemap_adjust(DORECOVER) -> chain==NULL -> read chain->error at freemap.c:1021 -> kernel page fault -> panic (DoS)",
 "evidence": [
  "panic.txt: 'Stopped at hammer2_freemap_adjust+0xce: movl 0x170(%rax),%r9d', fault VA 0x170 == offsetof(hammer2_chain_t, error)",
  "run.log: full fatal-trap block from serial console; guest down",
  "forge_2651.py: the three edits (blank freemap_blockset, mtid 0x11->0x20 on sroot+testvol brefs, CHECK_NONE ancestors, volhdr CRC recompute)",
  "fix_run.log: patched kernel #1 mounts the same image with MOUNT_OK and stays up"
 ],
 "kernel_refs": [
  "sys/vfs/hammer2/hammer2_freemap.c:972",
  "sys/vfs/hammer2/hammer2_freemap.c:1008",
  "sys/vfs/hammer2/hammer2_freemap.c:1016",
  "sys/vfs/hammer2/hammer2_freemap.c:1021",
  "sys/vfs/hammer2/hammer2_freemap.c:1037",
  "sys/vfs/hammer2/hammer2_vfsops.c:2234",
  "sys/vfs/hammer2/hammer2_vfsops.c:2325",
  "sys/vfs/hammer2/hammer2_chain.c:1627"
 ],
 "poc_changes": "Forge built on DF-2650 tooling (h2common.py). Two iterations needed: first run mounted cleanly because only the in-sroot-block testvol bref was bumped - recovery never recursed since the volhdr sroot_blockset bref (the recursion gate) still had mirror_tid 0x11; fixed by bumping the volhdr sroot bref too. Guest /mnt/h2t had to pre-exist before vnconfig (set -e aborted runs otherwise).",
 "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": 42,
 "guest_dirty": 0,
 "build_cmd": "python3 forge_2651.py base2651.img forge2651.img (host); guest: vnconfig -c vn0 /tmp/forge2651.img; mount -t hammer2 /dev/vn0@testvol /mnt/h2t",
 "run_cmd": "vm.sh run_root 'vnconfig -c vn0 /tmp/forge2651.img; mkdir -p /mnt/h2t; mount -t hammer2 /dev/vn0@testvol /mnt/h2t'",
 "code_hash": "02e383eb5bef20dca2a9daa4578357d8bcbdba030c2eba1b6f47e269a39e52fa",
 "notes": "offsetof(hammer2_chain_t, error) == 0x170 confirmed by the faulting instruction operand. Dedup path (chain.c:1627) reaches the same deref. Related create-error fallthrough at freemap.c:1037-1100 (chain_create failure still falls into chain->data deref) is covered by the same fix ordering; chain_create failure for reserved freemap blocks is not demonstrably reachable.",
 "recommended_fix": "In hammer2_freemap_adjust(), only test chain->error after ruling out chain == NULL; let DORECOVER fall through to the existing chain_create at :1037.",
 "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@dfbd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
 "fix_verdict": "fix.diff reorders the NULL handling in hammer2_freemap_adjust(): on kernel #1 the exact PoC image that fatal-faulted (movl 0x170(%rax)) on #0 mounts cleanly, files are created/read, unmount is clean, guest stays up. Baseline panic gone; no regression on clean images.",
 "fix_evidence": "fix_run.log (MOUNT_OK + probe file round-trip on #1); fix_build.log (nativekernel BUILD_RC=0, installkernel INSTALL_RC=0, uname #1); panic.txt for the #0 baseline"
}