rt2661_scan_end and rt2661_enable_tsf_sync dereference TAILQ_FIRST(&ic->ic_vaps) without a NULL check β panic racing last-vap destruction during the scanner's unlocked ic_scan_end call
Summary
Both sites deref TAILQ_FIRST(&ic->ic_vaps) with no NULL check. The scanner invokes ic_scan_end unlocked (self-annotated 'scan state can change!'), while vap_delete removes the last vap under the lock - destroying the only vap inside that window leaves vap==NULL -> Fatal trap 12. Privileged-triggered timing race (ifconfig destroy vs completing background scan) on any RT2561/RT2661 host. NULL-guard fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3968 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.0 KB | β raw | |
| VERDICT.md | β | 1.5 KB | β raw | |
| trigger.c | β | 1.7 KB | view raw | |
| build.sh | β | 67 B | view raw | |
| run.sh | β | 91 B | view raw | |
| run.log | β | 12 B | view raw | |
| env.txt | β | 1021 B | view raw | |
| fix.diff | β | 397 B | view raw | |
| build.log | β | 37.9 KB | view raw | |
| manifest.json | β | 370 B | view raw | |
| verdict.json | β | 3.0 KB | view raw |
DF-3968 β rt2661_scan_end / rt2661_enable_tsf_sync dereference TAILQ_FIRST(&ic->ic_vaps) unchecked β NULL-deref panic racing last-vap destroy
File: sys/dev/netif/ral/rt2661.c (pass 2)
What
rt2661_scan_end (2839β2848) and rt2661_enable_tsf_sync (2743β2750) fetch
the first vap and dereference it (vap->iv_bss->ni_bssid,
vap->iv_opmode) without a NULL check. The scanner calls ic_scan_end
unlocked (ieee80211_scan_sw.c:797, with its own "scan state can change!"
warning), while ieee80211_vap_delete β ieee80211_scan_vdetach
(ieee80211_scan.c:140β155) removes the vap under the lock. Destroying the
only vap inside that window leaves TAILQ_FIRST NULL β Fatal trap 12.
Build / Run
Hardware-gated (needs a Ralink NIC + association + background scan):
cc trigger.c (informational)
# on target: loop ifconfig wlan0 destroy against an active bg-scan
Expected
Fatal trap 12: page fault while in kernel mode at
rt2661_scan_end+β¦ (read of vap->iv_bss with vap==NULL).
DF-3968 verdict β certain NULL-check gap, race-gated DoS
Reproduced?
No Ralink NIC on the audit guest (env.txt), so the destroy-vs-scan race could not be exercised. Source-level certainty is high:
- rt2661.c:2843-2847:
vap = TAILQ_FIRST(&ic->ic_vaps); ... rt2661_set_bssid( sc, vap->iv_bss->ni_bssid);β no NULL check. - rt2661.c:2747-2750 (enable_tsf_sync):
vap = TAILQ_FIRST(...); if (vap->iv_opmode != ...)β no NULL check; reached from scan_end and from the RUN transition (newstate, which always has a vap). - Call site: ieee80211_scan_sw.c:781-797 β scan_end() drops the COM lock
before
ic->ic_scan_end(ic)and re-acquires after, explicitly warning that scan state can change during the driver call. - Tear side: ieee80211_scan.c:140-155 β ieee80211_scan_vdetach(vap) (under IEEE80211_LOCK, from ieee80211_vap_delete) clears ss_vap and the vap leaves ic_vaps; nothing re-validates the driver's TAILQ_FIRST snapshot.
β ifconfig wlanX destroy racing a completing background scan on the last
vap dereferences NULL β panic (local DoS; destroy is privileged).
Fix
fix.diff adds NULL guards at both sites (two-line defense).
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testableTwo two-line guards eliminate the NULL deref; git apply --check clean. Runtime validation needs the NIC fix.diff applied to the guest /usr/src copy and the if_ral.ko module rebuilt clean with -Werror (build.log; combined with the other two rt2661 fix.diffs of this batch); runtime validation still impossible without the NIC.
fix.diff; fix.diff applied to the guest /usr/src copy and the if_ral.ko module rebuilt clean with -Werror (build.log; combined with the other two rt2661 fix.diffs of this batch); runtime validation still impossible without the NIC.
Confirmed kernel references
Detail
Exploit chain
assoc + background scan running -> concurrent 'ifconfig wlan0 destroy' wins the unlocked ic_scan_end window -> ieee80211_vap_delete/scan_vdetach empties ic_vaps -> rt2661_scan_end reads vap->iv_bss at NULL -> kernel panic (DoS).
Evidence (decisive lines)
VERDICT.md (race window path:line incl. net80211 call site), trigger.c (on-target loop), env.txt, fix.diff (NULL guards at both sites, git apply --check clean)
PoC changes
Trigger is a documented race recipe (needs NIC + association); informational C stub compiled only.
Verified recommended fix
NULL-guard TAILQ_FIRST(&ic->ic_vaps) in rt2661_scan_end and rt2661_enable_tsf_sync (fix.diff).
Verdict
Certain missing NULL checks: rt2661_scan_end (rt2661.c:2843-2847) dereferences vap->iv_bss and rt2661_enable_tsf_sync (rt2661.c:2747-2750) dereferences vap->iv_opmode on TAILQ_FIRST(&ic->ic_vaps) without checking for an empty list; the scanner invokes ic_scan_end unlocked (ieee80211_scan_sw.c:797, self-annotated 'scan state can change!'), and ieee80211_scan_vdetach (ieee80211_scan.c:140-155) removes the last vap under the lock β destroying the only vap in that window yields vap==NULL -> Fatal trap 12 (privileged-triggered race, local DoS). Not reproduced: guest has no Ralink NIC (env.txt); trigger recipe (destroy-vs-bgscan loop) documented in trigger.c.
No comments yet.