⬢ DragonFlyBSD Kernel Audit
DF-2585 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2585",
  "status": "not_reproduced",
  "reproduced": false,
  "impact": "none",
  "confidence": "certain",
  "verdict": "NOT REPRODUCED, impact none. The unsynchronized read is real (hammer2_flush.c:1377-1384 loads ip->cluster.array[j].chain and derefs chain->hmp with no cluster_spin/ref, unlike the canonical accessor hammer2_inode_chain() at hammer2_inode.c:407-426), but the claimed UAF is not reachable as a race on this kernel: (1) the loop body executes only for clindex>=1, i.e. iroot->cluster.nchains>=2 - a multi-device cluster; single-device mounts never run it; (2) the ONLY producer of HAMMER2_XOP_VOLHDR is vfs_sync stage 2 (vfsops.c:2780-2786), which holds iroot->lock EXCLUSIVE across the whole chain_flush xop, so the backend executes the read while the frontend pins every lock-taking writer out; (3) every in-tree writer of iroot->cluster.array[] is serialized by that same lock (pfsalloc vfsops.c:455-500) or excluded by the xops inode reference (repoint-at-final-drop inode.c:640 cannot fire while the xop holds a ref; repoint_one is only called under nip->lock from hammer2_inode_gets found path, inode.c:886-899; repoint in create/connect paths only touches fresh inodes); (4) even under a manufactured race the use is a single non-faulting load from the kernel heap consumed by an equality comparison (flush.c:1379) whose result never escapes - no panic, leak, or corruption shape exists. Guest evidence: a 2-chain pmp was actually constructed via the join-mount path (dd-copied MASTER pair; second mount matched pmp=0xfffff80118e20000 then EBUSY PFS already mounted! leaving the cluster 2-wide; clindex-1 xop threads verified live via ps, 109 h2xop-CLUS threads); light probes worked; 60s of parallel file churn + direct iroot metadata churn against a tight sync loop produced NO crash, panic, or kernel message (only the independent local-multi-master quorum wedge: one sync parked in h2inode, guest alive at load 0.00). The multi-volume : syntax cannot even construct the precondition (duplicate volume id rejected). Recommended hardening (not a security fix): snapshot the chain pointers under cluster_spin with references (hammer2_inode_chain pattern) before the hmp comparison.",
  "exploit_chain": "",
  "evidence": "run.log (4-phase guest session: multi-volume rejection, join-mount EBUSY with shared pmp + 109 h2xop-CLUS threads incl. clindex-1 group, working light probes, 60s churn+sync stress with no manifestation); cluster2.txt (serial excerpt of the join-mount); VERDICT.md sections 2-5 (reachability gates + writer enumeration with path:line); hammer2_inode.c:407-426 vs hammer2_flush.c:1377-1384 (the missing pattern)",
  "kernel_refs": [
    "sys/vfs/hammer2/hammer2_flush.c:1377",
    "sys/vfs/hammer2/hammer2_flush.c:1378",
    "sys/vfs/hammer2/hammer2_flush.c:1379",
    "sys/vfs/hammer2/hammer2_flush.c:1368",
    "sys/vfs/hammer2/hammer2_vfsops.c:2780",
    "sys/vfs/hammer2/hammer2_vfsops.c:2783",
    "sys/vfs/hammer2/hammer2_inode.c:407",
    "sys/vfs/hammer2/hammer2_inode.c:426",
    "sys/vfs/hammer2/hammer2_inode.c:1365",
    "sys/vfs/hammer2/hammer2_inode.c:1459",
    "sys/vfs/hammer2/hammer2_inode.c:886",
    "sys/vfs/hammer2/hammer2_inode.c:899",
    "sys/vfs/hammer2/hammer2_vfsops.c:455",
    "sys/vfs/hammer2/hammer2_vfsops.c:1455"
  ],
  "poc_changes": "Rig authored from scratch (no seed). Cluster construction went through three attempts: per-device labels in : syntax (rejected - the parser takes ONE label after the last device), /dev/vn0:/dev/vn1@CLUS (rejected - duplicate volume id), and finally the join-mount path (mount vn0, then mount_hammer2 vn1 -> same pmp + EBUSY) which does produce a live 2-chain cluster.",
  "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": 1800,
  "guest_dirty": 0,
  "build_cmd": "guest(root): sh build_cluster.sh (attempts multi-volume + prepares the dd-copy MASTER pair on vn0/vn1)",
  "run_cmd": "guest(root): mount -t hammer2 /dev/vn0@CLUS /mnt/c2; mount_hammer2 /dev/vn1@CLUS /mnt/c2 (EBUSY, cluster 2-wide); sh stress.sh 60 in parallel with a tight `sync` loop",
  "code_hash": "8b9ae7a508c5e8559226dc539564b38cc4c87b7de0dcf78d8f123667ad3f0ed0",
  "notes": "Incidental observations for upstream (verdict notes only): (a) the vfsops.c:1455 PFS already mounted! EBUSY path leaves the joined chain attached to the live pmp with mount_count>0 - unmount_helper returns early - and the resulting 2-chain local cluster wedges under sustained sync+write (a sync parked in h2inode); a separate robustness bug worth its own look, not DF-2585. (b) A KLD rig that calls hammer2_inode_repoint() on iroot from a raw kernel thread would manufacture a racing writer that does not exist in-tree; not built because it would test a synthetic state, and the reads use-shape (equality compare, value never escapes) precludes observable harm even then. The findings code-level observation stands as a legitimate hardening item.",
  "recommended_fix": "Wrap the volume-dedup loop in the hammer2_inode_chain() pattern: take ip->cluster_spin shared, ref the array chains, drop the spin, then compare chain->hmp and drop the refs (mirrors hammer2_inode.c:407-426).",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "No fix authored: not a memory-corruption bug (not reproduced; no reachable racing writer; benign use-shape). The hardening change in recommended_fix is a one-liner pattern swap for upstream.",
  "fix_evidence": "VERDICT.md sections 2-5"
}