⬢ DragonFlyBSD Kernel Audit
DF-5294 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5294",
  "status": "inconclusive",
  "reproduced": false,
  "impact": "none",
  "confidence": "likely",
  "verdict": "rum_ratectl_task() ends with an UNCONDITIONAL usb_callout_reset of rvp->ratectl_ch (if_rum.c:3140) - no ratectl_run guard, unlike both siblings in the same tree (if_run.c:2497-2498, if_mtw.c:2401-2402, each also clearing the mask in vap_delete before draining: if_run.c:1017, if_mtw.c:895). rum_vap_delete() (:709-721) drains the callout (:716) BEFORE the task (:717), so a task instance completing after the callout drain re-arms the callout; the re-armed callout can fire inside the ratectl_deinit/ieee80211_vap_detach window (ether_ifdetach netisr round-trips; the ieee80211_stop->rum_newstate->usb_callout_stop at :934 comes at the END of that window) and enqueue the task again, and nothing drains ratectl_task after :719 - the task then runs rum_ratectl_task() on the kfreed rum_vap (:720), dereferencing vap->iv_ic/vap->iv_bss from freed heap, and re-arms the callout on the freed object, self-perpetuating (ieee80211_runtask writes into freed memory on every enqueue). The same missing guard also lets a task queued before a state transition re-arm the callout after rum_newstates stop (:934), so the ratectl loop keeps firing while the vap is down. In-kernel reproduction is hardware-gated (no RT2573 on the guest). Simulated with a line-faithful callout/taskqueue/destroyer protocol model: shipped code 115/108/125 post-free callout fires and 111/108/127 post-free task runs per 3x10s (360 destroy cycles each); the guarded (fix) protocol yields exactly 0/0.",
  "exploit_chain": "root destroys a RUNNING sta vap -> task completes after callout_drain, re-arms (:3140, unguarded) -> callout fires in the ether_ifdetach window -> task enqueued, survives past kfree(:720) -> rum_ratectl_task on freed M_80211_VAP memory: ieee80211_runtask enqueue writes into the freed (reallocatable, ~0x2c0-byte) object at fixed offsets + attacker-shaped vap->iv_ic deref -> heap corruption -> panic to privesc potential; root-triggered corruption primitive (vap destroy needs privilege)",
  "evidence": "run.log: shipped POST-FREE callout fires=115/108/125 POST-FREE task runs=111/108/127 vs guarded 0 ... 0 across 3+3 runs; rum_ratectl_drain.c models the exact drain order (:716->:717->detach-window->kfree) and the unguarded/guarded :3140; VERDICT.md cites the sibling guards",
  "kernel_refs": [
    "sys/bus/u4b/wlan/if_rum.c:3140",
    "sys/bus/u4b/wlan/if_rum.c:716",
    "sys/bus/u4b/wlan/if_rum.c:717",
    "sys/bus/u4b/wlan/if_rum.c:720",
    "sys/bus/u4b/wlan/if_rum.c:934",
    "sys/bus/u4b/wlan/if_rum.c:989",
    "sys/bus/u4b/wlan/if_run.c:2497",
    "sys/bus/u4b/wlan/if_mtw.c:2401",
    "sys/netproto/802_11/wlan/ieee80211.c:752",
    "sys/bus/u4b/usbdi.h:411"
  ],
  "poc_changes": "Harness written from scratch (no seed); condvar-based callout/tick and taskqueue threads model usb_callout semantics and the exact shipped destroy sequence, with timings scaled 1s->4ms; first draft had a join-deadlock and a missing wakeup, fixed before the recorded runs",
  "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": 120,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -pthread -Wall -o rum_ratectl_drain rum_ratectl_drain.c",
  "run_cmd": "./rum_ratectl_drain 10 0 (x3) then ./rum_ratectl_drain 10 1 (x3)",
  "code_hash": "2af92ff9a0d519138ec64cf193d4b604843deb80044fe3be79ae15e4a6f95d94",
  "notes": "Hardware-gated (wlan-family pass-2 methodology). fix.diff touches if_rum.c + if_rumvar.h (adds sc->ratectl_run, clears it in newstate and in vap_delete BEFORE the drains, guards :3140) - exactly the if_run/if_mtw protocol; overlaps DF-5292/5293 in rum_vap_delete (trivial merge).",
  "recommended_fix": "Guard the :3140 re-arm with a sc->ratectl_run flag that rum_newstate and rum_vap_delete clear BEFORE draining the callout/task pair (the if_run/if_mtw protocol)",
  "fix_status": "not_testable",
  "fix_baseline_reproduced": 0,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff authored and verified with git apply --check against the pristine sys/ tree (both files); kernel behavior cannot be exercised without an RT2573 device. The userland protocol model shows the guarded re-arm reduces post-free fires/task runs from ~110 per 10s to exactly 0. Hardware-gated: not_testable.",
  "fix_evidence": "fix.diff (git-apply clean, 2 files); run.log guarded-mode lines =0; VERDICT.md"
}