β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3968

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3968 Β· 11 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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_testable
baseline no→ patch + rebuild →patched clean

Two 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.
↓ fix.diffper-fix-DF-3968

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.