⬢ DragonFlyBSD Kernel Audit
DF-3454 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-3454",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "Reproduced on the QEMU guest via a KLD harness that drives only the public ieee80211_ratectl_* API against the in-kernel amrr (the DEFAULT ratectl, ratectl.c:119). After an 11n association is driven to amn_rix=76 through real success intervals, flipping the node to a legacy channel makes amrr_rate()/amrr_update()/amrr_node_stats() index the 15-byte ni_rates.rs_rates with the stale 76 (array ends at 14): all 8 bytes of the ni_chan kernel heap pointer (rs_rates[18..25]) and planted 0xA5 meshid markers are read out of bounds and disclosed 7-bits-masked through ni_txrate -> SIOCG80211NODE isi_txrate and the world-readable net.wlanX.rate_stats sysctl. The empty-rateset tmp-node path yields amn_rix=-1 returned to drivers (iwm_setrates KASSERT -> INVARIANTS panic). Read-only intra-object OOB inside the 12208-byte ieee80211_node allocation: no write primitive, not an escalation candidate. Fix (clamping amn_rix/rix against the active rateset at amrr_update/amrr_rate/amrr_node_stats) validated on a rebuilt kernel: rix 76->11, -1->0, zero OOB reads, identical PoC, guest stable.",
  "exploit_chain": "remote 11n peer (or DFS/CSA legacy channel switch) flips node HT status while amn_rix is an htrates index (<=76) -> stale index used against 15-entry legacy rs_rates in wlan core (OOB read, value -> ni_txrate) -> unprivileged local user reads net.wlanX.rate_stats (CTLFLAG_RD) / SIOCG80211NODE isi_txrate -> 7 of 8 bits per disclosed byte incl. ni_chan heap pointer bytes; driver-side: run(4) indexes rt2860_rates[12] with rix<=76 (static OOB read), bwn(4) reads rs_rates[rix-1], iwm(4) KASSERT(rix>=0&&rix<nrates) panics INVARIANTS kernels. Ceiling: KASLR-defeating pointer-byte harvest + driver metadata corruption/panic - no write primitive.",
  "evidence": "run.2.log: PHASE3 returned rix=76; legacy rs_nrates=12 + rs_rates[76] ... is OOB (array ends at rs_rates[14]); PHASE4 lines rix=25..18 ... [ni_chan pointer byte] showing bytes ff ff f8 00 8d 40 3a 40 of ni_chan=0xfffff8008d403a30 and 34 x [0xA5 meshid marker] lines reading 0xa5 disclosed as 0x25; PHASE5 amn_rix=-1 ratectl_rate() returned -1. fix_run.log (patched kernel #1): returned rix=11, rate_stats rate: 54 Mbit (=108/2, real top legacy rate), zero PHASE4 OOB lines, amn_rix=0 ... returned 0. fix_module_only.log documents the failed module-only patch attempt (amrr is compiled into the base kernel via sys/conf/files:1630 optional wlan).",
  "kernel_refs": [
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:145",
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:208",
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:259",
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:339",
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:350",
    "sys/netproto/802_11/wlan/ieee80211_amrr.c:434",
    "sys/netproto/802_11/wlan/ieee80211_ratectl.c:119",
    "sys/netproto/802_11/wlan/ieee80211_node.c:1276",
    "sys/netproto/802_11/_ieee80211.h:375",
    "sys/netproto/802_11/_ieee80211.h:388",
    "sys/dev/netif/bwn/bwn/if_bwn.c:6304",
    "sys/bus/u4b/wlan/if_run.c:2617",
    "sys/bus/u4b/wlan/if_run.c:3345",
    "sys/dev/netif/iwm/if_iwm.c:4366"
  ],
  "poc_changes": "Harness authored fresh (no seed PoC existed). Two build fixes vs first draft (missing net/if_media.h include for ifmedia in ieee80211_var.h; NULL->0 in DEV_MODULE). One harness logic fix after run 1 (walk-down step budget exhausted early - steps counter reuse; run 2 walks the full 76->17 range exposing all 8 ni_chan pointer bytes). Cosmetic: harness is OOB label prints unconditionally for PHASE3 even when the index is in-bounds after the fix (fix_run.log rix=11 vs array end 14 - in bounds). iv_rs setup replicates amrr_init()s 4 statements because ir_inits sysctl attach requires a fully constructed vap sysctl tree; the vulnerable functions exercised (node_init/update/rate/tx_update/node_stats/setinterval) are the real in-kernel ones, reached through the public wrappers.",
  "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 (baseline) / #1: Sun Sep 13 02:35:55 UTC 2026 (patched fix-validation kernel)",
  "runtime_sec": 150,
  "guest_dirty": 1,
  "build_cmd": "scp amrroob.c Makefile dfbsd:/root/amrroob/ && cd /root/amrroob && make SYSDIR=/usr/src/sys",
  "run_cmd": "kldload /root/amrroob/amrroob.ko && sysctl amrroob.run=1; dmesg | grep amrroob; kldunload amrroob",
  "code_hash": "236304168a7606df4b9b5de8227b82988eb456463c857ec56d988ce6cd53659b",
  "notes": "amrr is the DEFAULT ratectl on DragonFly (ieee80211_ratectl.c:119-120) and ieee80211_amrr.c is compiled into the base kernel (sys/conf/files:1630 optional wlan) - patching wlan_amrr.ko alone has NO effect (documented in fix_module_only.log); fix validation required make nativekernel + installkernel + reboot. Baseline reproduced twice (run.log partial walk, run.2.log full disclosure x2 executions). Guest left dirty (patched kernel + /usr/src patch + /root/amrroob) - reset with-src REQUIRED after this run (orchestrator). vm.sh down hangs (hard-kill) on this guest: buffer-cache data must be syncd before down or files truncate (first wlan_amrr.ko install was lost that way).",
  "recommended_fix": "Clamp amn_rix/rix against the currently selected rateset at every use (amrr_update, amrr_rate, amrr_node_stats) - see fix.diff; also make drivers treat rix<0 as use lowest.",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Sun Sep 13 02:35:55 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 (patch -p1, 3 hunks), make nativekernel (RC=0, fix_build.log), make installkernel, reboot into kernel #1. Identical PoC re-run (fix_run.log): PHASE3 ratectl_rate() returns rix=11 = rs_nrates-1 (was 76); rate_stats prints rate: 54 Mbit = the genuine top legacy rate 108/2 (was leaked OOB bytes); PHASE4 walk-down produces zero OOB reads (loop never reaches the pointer/marker range 18..76); PHASE5 empty-rateset tmp node yields amn_rix=0 and ratectl_rate() returns 0 (was -1, the iwm KASSERT panic trigger). Bad behavior gone; guest stable before/after.",
  "fix_evidence": "fix_run.log (patched dmesg: returned rix=11, rate: 54 Mbit, amn_rix=0 ... returned 0, no PHASE4 OOB lines) vs run.2.log (baseline: rix=76, pointer bytes 40 3a 40 8d 00 f8 ff ff at rs_rates[18..25], 0xA5 markers as 0x25, rix=-1); fix.diff (git-apply clean); fix_build.log (nativekernel tail, RC=0); fix_module_only.log (module-only patch attempt - no effect, proves in-kernel amrr)."
}