DF-5294 / verdict.json
{ "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" } |