⬢ DragonFlyBSD Kernel Audit
DF-3749 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3749",
  "status": "not_reproduced",
  "reproduced": 0,
  "impact": "none",
  "confidence": "likely",
  "verdict": "rt2860_newassoc (rt2860.c:887) stores an unrefcounted node pointer in sc->wcid2ni[] and nothing ever clears it: rt2860_node_free (rt2860.c:897-911) wipes only the hardware WCID table, stop/reset never touch the array (whole-driver grep: set at :887, read at :1137 only). rt2860_drain_stats_fifo (rt2860.c:1133-1165) runs on every TX interrupt and dereferences the entry -- ni->ni_vap at :1157/:1160/:1162 and amrr_tx_completes write through ni->ni_rctls (ieee80211_amrr.c:363-370) -- so any TX-status FIFO entry for a torn-down station processed after the free is a use-after-free read+write. The dangling pointer is 100% guaranteed on every station teardown (source-certain); the deref needs a stat latched/drained across the teardown window (TXD-done vs MCU stat-FIFO ordering, deauth mid-interrupt), which an attacker drives by repeated associate/traffic/deauth cycles against a ral(4) hostap. Not reproduced only because the audit guest has no Ralink NIC (env.txt).",
  "exploit_chain": "attacker station associates (wcid2ni[w]=ni) -> traffic so AP transmits to it (stats latched) -> attacker deauths; RX intr frees node (node_free clears HW WCID but not wcid2ni[w]) -> a late-drained TX_STAT_FIFO entry for w yields freed ni -> ni->ni_vap deref + amrr write through ni->ni_rctls read from freed M_80211_NODE memory -> reclaim chunk via new association nodes (attacker-influenced fields) to steer amn/vap -> increment-style writes at fixed offsets / indirect call vap->iv_rate->ir_tx_complete; crash at minimum, groomed code-execution plausible on the no-SMAP class guest. Undemonstrated without hardware.",
  "evidence": "VERDICT.md (static proof + interleaving analysis), README.md (deauth-under-load recipe), fix.diff (clears wcid2ni[wcid] in node_free; git apply --check passes)",
  "kernel_refs": [
    "sys/dev/netif/ral/rt2860.c:887",
    "sys/dev/netif/ral/rt2860.c:897",
    "sys/dev/netif/ral/rt2860.c:1133",
    "sys/dev/netif/ral/rt2860.c:1137",
    "sys/dev/netif/ral/rt2860.c:1157",
    "sys/dev/netif/ral/rt2860.c:1162",
    "sys/dev/netif/ral/rt2860.c:1174",
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:363"
  ],
  "poc_changes": "No PoC source: needs 802.11 radio + Ralink NIC in hostap mode. Recipe documented in README.md.",
  "attempts": 1,
  "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": 10,
  "guest_dirty": 0,
  "build_cmd": "",
  "run_cmd": "dfbsd-qemu/vm.sh run_root pciconf -lv | grep ralink; ifconfig -l",
  "code_hash": "",
  "notes": "Pass-2 depth finding (lifetime across teardown vs interrupt state machine). fix.diff composes with DF-3748s array widening (with wcid2ni[256] the node_free clear is index-safe for any truncated wcid).",
  "recommended_fix": "Clear sc->wcid2ni[wcid] = NULL in rt2860_node_free next to the existing hardware WCID entry clear.",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "One-line lifetime fix, compile-consistent, mirrors the OpenBSD rt2860-family fix shape; runtime validation impossible on this guest (no NIC).",
  "fix_evidence": "fix.diff (git apply --check passes against pristine sys/)"
}