DragonFlyBSD Kernel Audit
DF-2707 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2707",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Peer-driven kernel memory corruption in kern_dmsg.c on production (non-INVARIANTS) kernels: while the iocom write thread is parked in fp_write (peer withholds reads), each absorbed duplicate DELETE re-runs the state callback and kdmsg_msg_reply()'s unlocked txcmd check queues another terminating reply; on drain, kdmsg_state_cleanuptx() re-runs the close sequence (msgtx's txcmd reset at :1571 defeats the :1671 assert), RB_REMOVEs the already-removed state (stale-pointer writes into the live staterd_tree) and drops a phantom rbtree ref, prematurely freeing the state while further queued replies still reference it (UAF). Demonstrated: Fatal trap 12 page fault in kdmsg_state_tree_RB_REMOVE_COLOR on the non-INVARIANTS kernel (panic.txt); identical input on stock INVARIANTS panics earlier at :1076 (DF-0018's assert), which masked this continuation. Fix (RBINSERTED-invariant guard at the RB_REMOVE site) validated on a rebuilt non-INVARIANTS kernel: baseline panics, patched completes 3+6 rounds of 24-40 dup-DELETEs with clean unmount and no errors.",
  "exploit_chain": "cluster peer (cluster_fd mount hook / service-daemon link / remote node) -> park writer (SO_SNDBUF small or just stop reading; junk reply-generating transactions) -> open victim LNK_SPAN (peer_type=HAMMER2 proto=1, stays open) -> N duplicate DELETEs for its msgid (each absorbed duplicate queues REPLY|CREATE|DELETE) -> peer drains -> cleanuptx#2 double RB_REMOVE (tree corruption) + phantom refdrop -> premature kdmsg_state_free -> replies #3..#N write into freed 160-byte-class slot (txcmd |= DELETE, refs--, further stale RB_REMOVEs); slot reclaimable by peer-created states (same mmsg zone) compounding corruption. Full uid=0 chain not constructed: attacker position is a cluster peer (local route requires root mount), and reclaimed kdmsg_state_t content is kernel-chosen except msgid (offset 120) - documented as the practical ceiling rather than a hard blocker.",
  "evidence": ["panic.txt (Fatal trap 12 in kdmsg_state_tree_RB_REMOVE_COLOR, non-INVARIANTS kernel #1)", "panic.invariants_baseline.txt (stock INVARIANTS: same input -> DF-0018 assert at :1076)", "run.attack2_ssh.log + run.attack2_trigger.out (second unfixed run: post-drain iocom reader wedge)", "run.wedge_probe.txt (single-DELETE control: DF-2710 stall isolated from this corruption)", "run.fix.log + run.fix.2.log (fix kernel: attack 3x24 and 6x40 all rounds complete, UMOUNT_RC=0, no panic)", "fix.diff (RBINSERTED guard, validated)"],
  "kernel_refs": ["sys/kern/kern_dmsg.c:1075-1077", "sys/kern/kern_dmsg.c:2056-2058", "sys/kern/kern_dmsg.c:1558-1573", "sys/kern/kern_dmsg.c:1670-1707", "sys/kern/kern_dmsg.c:1195-1211", "sys/sys/tree.h:585-660", "sys/sys/systm.h:93-118"],
  "poc_changes": "Adapted the DF-2614 cluster_fd mount harness; added SO_SNDBUF-based writer parking, junk reply-generating transactions, victim SPAN state (peer_type=HAMMER2, proto=1 so kdmsg_msg_result keeps it open), N duplicate DELETEs, drain+concurrent state-spray, tree churn, plus leak/wedge control modes. Fixed the CONN reply flags (needs REVTRANS) was unnecessary - an unanswered CONN does not affect the attack.",
  "attempts": 5,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0/#1/#2 X86_64_GENERIC x86_64 (stock INVARIANTS #0; non-INVARIANTS #1 baseline; non-INVARIANTS+fix #2)",
  "runtime_sec": 2100,
  "guest_dirty": 0,
  "build_cmd": "cc -O -I/usr/src/sys -o df2707_trigger df2707_trigger.c ; sed -i 's/^options \tINVARIANTS/#options \tINVARIANTS/' /usr/src/sys/config/X86_64_GENERIC ; cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC && make installkernel KERNCONF=X86_64_GENERIC",
  "run_cmd": "sh /root/poc/df2707/run_df2707.sh attack 3 24   (then: attack 6 40; controls: leak, wedge)",
  "code_hash": "084a8b6bdb7eb1553cc425e667b44d08a71319384f1737548a40ebac95a1c6e6",
  "notes": "Impact recorded as panic (observed); the underlying primitive is write-capable memory corruption (double-unlink + refcount-underflow -> UAF) with code-execution ceiling on clustered deployments. INVARIANTS kernels convert the same peer input into DF-0018's :1076 assert. Guest reset with-src performed after validation.",
  "recommended_fix": "In kdmsg_state_cleanuptx(), discard a transmitted DELETE for an already fully-closed state: inside 'if (state->rxcmd & DMSGF_DELETE)', if !(flags & KDMSG_STATE_RBINSERTED), free the msg, drop the cleanuptx hold, and return (see fix.diff).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #2: Mon Aug 31 03:35:20 UTC 2026 (non-INVARIANTS + fix.diff)",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Baseline non-INVARIANTS kernel #1 panicked in kdmsg_state_tree_RB_REMOVE_COLOR under attack 3x24; after applying only fix.diff (guard at :1689) and rebuilding, the identical attack completed all rounds with TRIGGER_RC=0/UMOUNT_RC=0, and a heavier 6x40 stress run also completed with the guest healthy. fix_patched_reproduced=0 means the BUG did not reproduce on the fix kernel (expected/desired).",
  "fix_evidence": ["run.fix.log", "run.fix.2.log", "fix.diff"]
}