DragonFlyBSD Kernel Audit
DF-2620 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2620",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Verified end-to-end on the stock INVARIANTS kernel. (A) Up-direction: a crafted PFS inode with pfs_nmasters=0xFF is ingested unclamped at hammer2_vfsops.c:527-528; mount succeeds and the next unmount (reached via shutdown -> vfs_unmountall, since path-based umount crosses VFS_ROOT which the inflated quorum blocks forever) makes hammer2_xop_helper_cleanup() walk thrs[0..254] over a 288-slot/18432-byte allocation, page-faulting at hammer2_xop_helper_cleanup+0x5a on the exact OOB thrs[i].td read (cmpq $0,0x20(%rax,%r15,1)); garbage .td slots additionally drive hammer2_thr_delete() on OOB memory (atomic bit-set write to arbitrary adjacent heap words, thr->pmp=NULL write, kfree of a wild thr->scratch, OOB KKASSERT), observed in an earlier run as a permanent umount hang in h2twait. (B) Down-direction: a same-pfs_clid MASTER+SLAVE 2-device cluster gives pmp->pfs_nmasters=1 < nchains=2; umount -f deletes only the thrs[0] column and kfrees xop_groups with the SLAVE column's 36 live kernel threads inside (pfsdealloc/pfsfree cleanup skipped because xop_groups is already NULL) -> Fatal trap 12 in hammer2_primary_xops_thread+0x2d9 (lock xaddl on a wild pointer) during umount. Both panics captured on serial console. Fix (clamp at ingestion + cluster-width-bounded cleanup loop) validated by guest kernel rebuild: variant A reboots cleanly with zero traps; variant C no longer corrupts memory (a residual umount wait on second-chain worker teardown is pre-existing and reproduces identically with an unmodified MASTER+MASTER clone pair where the fix is inert). Root-cause chain mount->panic is deterministic; uid=0 chain not developed (OOB write offset is stride-locked past a specific 18KB allocation; UAF victims are kernel thread structs polled by 36 live threads).",
  "exploit_chain": "mount crafted image (root, or unprivileged with vfs.usermount=1 + owned device) -> pmp->pfs_nmasters = on-disk value, unclamped -> umount (via shutdown, or directly for nmasters<=1 clusters) -> hammer2_xop_helper_cleanup walks/frees xop_groups with wrong bounds -> OOB read (page fault, panic), OOB bit-set write + wild kfree on garbage .td slots (silent heap corruption / unkillable hang), or 36 live kernel threads left executing inside an 18KB freed block (UAF, worker panic). Deterministic kernel memory-safety violation; escalation not demonstrated.",
  "evidence": [
    "panic.txt: 'Fatal trap 12 ... Stopped at hammer2_xop_helper_cleanup+0x5a: cmpq $0,0x20(%rax,%r15,1)' (stock kernel, variant A, process 1 during vfs_unmountall)",
    "panic_C.txt / boot_after_C.log: 'Fatal trap 12 ... Stopped at hammer2_primary_xops_thread+0x2d9: lock xaddl %edx,0x81558(%rsi)' (stock kernel, variant C during umount -f)",
    "boot_after_A.log: full serial log of the A run (mount of forged /dev/vn0@testvol then the trap)",
    "C_run.log: variant C run log ending at the fatal umount -f; T1=37 -> T2=109 thread counts show the same-clid merge",
    "fixA_run.log + guest boot.log after it: patched kernel #1, same trigger A, clean reboot, grep -c 'Fatal trap' == 0",
    "fixC_run.log: patched kernel #1, trigger C, no panic; umount waits in D-state (h2twait) on second-chain teardown -- verified pre-existing by an unmodified MASTER+MASTER clone pair (fix inert, identical hang)",
    "fix_build.log: full untrimmed nativekernel build with fix.diff applied",
    "forge_df2620.py + df2620_dump.py: image forger/verifier (pfs_nmasters=0xFF at inode+0x86; pfs_type patch at +0x87; CHECK_NONE brefs; volhdr CRC32C recompute)"
  ],
  "kernel_refs": [
    "sys/vfs/hammer2/hammer2_vfsops.c:527",
    "sys/vfs/hammer2/hammer2_vfsops.c:528",
    "sys/vfs/hammer2/hammer2_vfsops.c:541",
    "sys/vfs/hammer2/hammer2_vfsops.c:566",
    "sys/vfs/hammer2/hammer2_vfsops.c:655",
    "sys/vfs/hammer2/hammer2_vfsops.c:683",
    "sys/vfs/hammer2/hammer2_admin.c:254",
    "sys/vfs/hammer2/hammer2_admin.c:261",
    "sys/vfs/hammer2/hammer2_admin.c:451",
    "sys/vfs/hammer2/hammer2_admin.c:461",
    "sys/vfs/hammer2/hammer2_admin.c:463",
    "sys/vfs/hammer2/hammer2.h:1065",
    "sys/vfs/hammer2/hammer2_disk.h:972",
    "sys/vfs/hammer2/hammer2_cluster.c:348"
  ],
  "poc_changes": "Finding seed had no runnable PoC. Built: (1) newfs_hammer2 base image + host-side python forger (DF-2616 CHECK_NONE technique) patching the testvol PFS inode's meta.pfs_nmasters (0xFF) or meta.pfs_type (SLAVE) with volhdr CRC32C recomputation; (2) discovered the claimed naive trigger (mount; umount) cannot reach cleanup for nmasters>8 because any path-based umount/stats cross VFS_ROOT, which loops forever on the inflated quorum (vfsops.c:1966-2008) -- variant A therefore triggers via shutdown/vfs_unmountall; (3) down-direction needed a usable mount: same-pfs_clid 2-device merge (mount #2 fails EBUSY AFTER merging its chain), giving nmasters=1 < nchains=2 with quorum satisfied; (4) fix validation required make nativekernel (buildkernel demands a missing buildworld).",
  "attempts": 9,
  "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": 4690,
  "guest_dirty": 0,
  "build_cmd": "python3 findings/poc/DF-2620/forge_df2620.py base.img A A.img ; python3 findings/poc/DF-2620/forge_df2620.py base.img C C.img",
  "run_cmd": "guest root: sh trigger_A.sh   (mount forged pfs_nmasters=0xFF image; shutdown -r now)  |  sh trigger_C.sh (same-clid MASTER+SLAVE merge via second mount; umount -f)",
  "code_hash": "4e3117f84795a844726e9d70e0e3c7acc600bc2fd73f4116dbc8503204d73fe4",
  "notes": "Side effect observed, same ingestion site: nmasters>=2 on a single-chain mount wedges the first VFS_ROOT forever (wchan h2root, vfsops.c:1966-2008) and pins the mountpoint's namecache lock (subsequent lookups D-state on ncplk) -- separate robustness bug, not fixed here, flagged for a new finding. Also flagged: umount -f of a same-clid 2-device cluster hangs in hammer2_thr_wait even with completely unmodified images (MASTER+MASTER clone, wchan h2twait) -- pre-existing multi-chain teardown defect, independent of DF-2620. Guest left in clean with-src state (stock kernel #0 restored); patched kernel #1 and build products lived only in the pre-reset disk state. vfs.usermount=0 on this guest, so PoC mounts ran as root, matching the finding's stated preconditions.",
  "recommended_fix": "Clamp ripdata->meta.pfs_nmasters to HAMMER2_MAXCLUSTER at ingestion (hammer2_vfsops.c:527-529) and bound hammer2_xop_helper_cleanup()'s loop by min(iroot->cluster.nchains, HAMMER2_MAXCLUSTER) instead of pfs_nmasters (hammer2_admin.c:461) -- see fix.diff.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Aug 28 14:29:04 UTC 2026  root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied to guest /usr/src, kernel rebuilt (make nativekernel, full log fix_build.log) and booted as #1. Baseline on stock #0: variant A panics in hammer2_xop_helper_cleanup+0x5a, variant C panics in hammer2_primary_xops_thread+0x2d9 (both captured). Patched: variant A (mount + shutdown -r) reboots cleanly with zero Fatal traps -- panic eliminated; variant C (merge + umount -f) no longer corrupts memory or panics -- cleanup now deletes both thread columns before kfree. Residual: variant C's umount -f ends up waiting forever (D-state, h2twait) on second-chain worker teardown; verified pre-existing and unrelated by running the identical umount on an unmodified MASTER+MASTER clone pair (no forged bytes, clamp inert, stock-identical cleanup sequence) which hangs the same way. Memory-safety defect: fixed.",
  "fix_evidence": [
    "fix_build.log (full nativekernel build, BUILD_RC=0)",
    "fixA_run.log + serial boot.log of the patched reboot: clean shutdown/reboot, grep -c 'Fatal trap' == 0",
    "fixC_run.log: no panic on patched kernel; thread count drops as columns are deleted (109 -> 73 -> ...)",
    "pre-existing-hang control: unmodified MASTER+MASTER clone pair, umount -f stuck in h2twait on patched kernel where the fix is provably inert"
  ]
}