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

rt2560_scan_end and rt2560_enable_tsf_sync dereference the vap without a NULL check β€” panic racing last-vap destruction during the scanner's unlocked ic_scan_end call (DF-3968 family)

Summary

rt2560_scan_end dereferences ic->ic_scan->ss_vap->iv_bssid and rt2560_enable_tsf_sync dereferences TAILQ_FIRST(&ic->ic_vaps)->iv_bss->ni_intval with no NULL guard. The scanner calls ic_scan_end with the COM lock dropped (self-annotated 'scan state can change!') while scan_vdetach clears ss_vap and empties ic_vaps under the lock. Destroying the only vap inside that window NULL-derefs -> Fatal trap 12. Identical to sibling DF-3968 (rt2661); rt2560.c even guards the same TAILQ_FIRST correctly elsewhere. Privileged-triggered race (ifconfig destroy vs completing bg scan). NULL-guard fix (module rebuilt -Werror clean).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3969 Β· 11 files
FileTypeDescriptionSize
README.md β€” 1.5 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
trigger.c β€” 2.1 KB view raw
build.sh β€” 67 B view raw
run.sh β€” 91 B view raw
run.log β€” 317 B view raw
env.txt β€” 317 B view raw
fix.diff β€” 553 B view raw
build.log β€” 37.9 KB view raw
manifest.json β€” 698 B view raw
verdict.json β€” 3.3 KB view raw

DF-3969 β€” rt2560_scan_end / rt2560_enable_tsf_sync dereference the vap unchecked β†’ NULL-deref panic racing last-vap destroy (DF-3968 family)

File: sys/dev/netif/ral/rt2560.c (pass 2, GLM 5.3)

What

rt2560_scan_end (2566–2574) dereferences ic->ic_scan->ss_vap->iv_bss and rt2560_enable_tsf_sync (2274–2284) dereferences TAILQ_FIRST(&ic->ic_vaps)->iv_bss with no NULL check. The scanner invokes ic->ic_scan_end unlocked (ieee80211_scan_sw.c:796–798, self-annotated "scan state can change!"), while ieee80211_vap_delete β†’ ieee80211_scan_vdetach (ieee80211_scan.c:147–155) clears ss_vap and empties ic_vaps under the COM lock. Destroying the only vap inside that window β‡’ NULL deref β‡’ Fatal trap 12 (local DoS; destroy is privileged). Identical pattern to DF-3968 on the rt2661 sibling; the rt2560 variant faults on whichever of the two derefs the race leaves stale.

Build / Run

Hardware-gated (needs an RT2560 PCI NIC; this guest has none β€” env.txt): cc trigger.c (informational) # on target: loop ifconfig wlan0 destroy against an active background scan

Expected

Fatal trap 12: page fault while in kernel mode in rt2560_enable_tsf_sync (read of vap->iv_bss with vap==NULL) or rt2560_scan_end (ss_vap==NULL).

Fix validation

fix.diff adds NULL guards at both sites; git apply --check clean against sys/, and applied to the guest's /usr/src copy where the full if_ral.ko module rebuilt clean with -Werror (build.log).

VERDICT.md
↓ download raw

DF-3969 verdict β€” certain NULL-check gap, race-gated DoS (rt2560 sibling of DF-3968)

Reproduced?

No Ralink NIC on the audit guest (env.txt: pciconf shows no vendor 0x1814 device), so the destroy-vs-scan race could not be exercised. Source-level certainty is high and the pattern is the proven DF-3968 family applied to the rt2560 driver:

  • rt2560.c:2566-2574: vap = ic->ic_scan->ss_vap; ... rt2560_set_bssid(sc, vap->iv_bss->ni_bssid); β€” no NULL check; rt2560_enable_tsf_sync(sc) is called first and derefs its own unchecked vap first.
  • rt2560.c:2277,2284: vap = TAILQ_FIRST(&ic->ic_vaps); ... tmp = 16 * vap->iv_bss->ni_intval; β€” no NULL check.
  • Call site: ieee80211_scan_sw.c:796-798 β€” scan_end() drops the COM lock around 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:147-155 β€” ieee80211_scan_vdetach(vap) (under IEEE80211_LOCK, from ieee80211_vap_delete) sets ss->ss_vap = NULL and the vap leaves ic_vaps; nothing re-validates either the driver's TAILQ_FIRST snapshot or its ss_vap copy.
  • rt2560.c:2691 (init_locked) shows the codebase already knows the pattern needs a guard: rt2560_set_macaddr(sc, vap ? vap->iv_myaddr : ic->ic_macaddr) β€” the two functions fixed here simply lack it.

β‡’ ifconfig wlanX destroy racing a completing background scan on the last vap β‡’ NULL deref β‡’ panic (local DoS; destroy is privileged). Not exploitable beyond DoS: both faults are pure reads at small fixed offsets of NULL.

Fix

fix.diff adds NULL guards in rt2560_scan_end (early return when ss_vap is NULL) and rt2560_enable_tsf_sync (early return when TAILQ_FIRST is NULL) β€” two-line defense, same shape as the accepted DF-3968 fix. Validated: git apply --check clean on sys/; applied on the guest /usr/src copy and the if_ral.ko module rebuilt clean with -Werror (build.log). Runtime validation needs the NIC.

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 NULL guards eliminate the deref; git apply --check clean against sys/ and the patched module rebuilt -Werror clean (build.log; combined with the other two rt2560 fix.diffs of this batch). Runtime validation impossible without the NIC.

fix.diff; build.log (if_ral.ko linked from patched rt2560.o with -Werror on the guest /usr/src copy)
↓ fix.diffper-fix-DF-3969

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 clears ss_vap and empties ic_vaps -> rt2560_enable_tsf_sync reads vap->iv_bss at NULL (2284) or rt2560_scan_end reads ss_vap->iv_bss (2573) -> kernel panic (DoS).

Evidence (decisive lines)

VERDICT.md (race window path:line incl. net80211 call site and the guarded sibling line rt2560.c:2691), trigger.c (on-target loop), env.txt (no Ralink NIC), fix.diff (NULL guards at both sites), build.log (if_ral.ko rebuilt -Werror clean on the guest /usr/src copy)

PoC changes

Trigger is a documented race recipe (needs NIC + association); informational C stub compiled only.

Verified recommended fix

NULL-guard ss_vap in rt2560_scan_end and TAILQ_FIRST in rt2560_enable_tsf_sync (fix.diff).

Verdict

Certain missing NULL checks: rt2560_scan_end (rt2560.c:2566-2574) dereferences ic->ic_scan->ss_vap->iv_bss and rt2560_enable_tsf_sync (rt2560.c:2277,2284) dereferences TAILQ_FIRST(&ic->ic_vaps)->iv_bss without checking for NULL; the scanner invokes ic_scan_end unlocked (ieee80211_scan_sw.c:796-798, self-annotated 'scan state can change!') and ieee80211_scan_vdetach (ieee80211_scan.c:147-155) clears ss_vap and empties ic_vaps under the lock β€” destroying the only vap in that window yields NULL -> Fatal trap 12 (privileged-triggered race, local DoS). Identical pattern to the proven sibling finding DF-3968 (rt2661); rt2560 even guards the same TAILQ_FIRST elsewhere (rt2560.c:2691). Not reproduced: the audit guest has no Ralink NIC (env.txt); the on-target trigger recipe (destroy-vs-bgscan loop) is documented in trigger.c.