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

Unlocked ss_ops dispatch TOCTOU: ieee80211_scan_timeout/iterate/flush check-then-call ss->ss_ops with no com lock while ieee80211_scan_vdetach's scan_detach kfrees ss->ss_priv (never cleared) - scan_age on freed table

Summary

ieee80211_scan_timeout is invoked from the ieee80211_node_timeout callout which holds NO com lock; scan_iterate and scan_flush dispatch unlocked the same way. The teardown ieee80211_scan_vdetach holds the lock, calls scan_detach -> sta_detach which kfrees ss->ss_priv (the whole sta_table) WITHOUT clearing it, then sets ss_ops=NULL. The check-then-call window lets the timer's scan_age run the table lock + TAILQ walks on freed memory. DF-0587 family (lock-discipline hole at a new site); root-gated trigger (vap destroy) racing the inact timer. Not runnable on this guest (no 802.11 radio; DF has no virtual 802.11 driver) - untested with interleaving diagram in pack. Fix: IEEE80211_LOCK around the unlocked dispatches + clear ss_priv at the top of sta_detach.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3437 Β· 3 files
FileTypeDescriptionSize
README.md β€” 2.2 KB ↓ raw
verdict.json β€” 2.2 KB view raw
manifest.json β€” 730 B view raw

DF-3437 β€” wlan scan dispatchers run ss_ops->… without the com lock, racing ieee80211_scan_vdetach's scan_detach which frees ss->ss_priv

sys/netproto/802_11/wlan/ieee80211_scan.c dispatches through ss->ss_ops without IEEE80211_LOCK in:

  • ieee80211_scan_timeout (:582-588) β€” called from the ieee80211_node_timeout callout, which takes no com lock (ieee80211_node.c:2243-2256);
  • ieee80211_scan_iterate (:626-633) and ieee80211_scan_flush (:639-647).

The teardown side, ieee80211_scan_vdetach (:137-155), does hold the lock and calls ss->ss_ops->scan_detach(ss) β†’ sta_detach (ieee80211_scan_sta.c:186-199) which kfrees ss->ss_priv (the whole sta_table) and never clears ss->ss_priv; ss->ss_ops = NULL happens only afterwards in vdetach. Interleaving:

callout:  if (ss->ss_ops != NULL)      /* unlocked read - true  */
vdetach:  (lock) scan_detach(ss) -> sta_detach: kfree(ss->ss_priv)
vdetach:  ss->ss_ops = NULL; (unlock)
callout:  ss->ss_ops->scan_age(ss)     /* stale ops; sta_age derefs
                                          the freed ss_priv under
                                          SCAN_TABLE_LOCK(st) on freed
                                          memory */

scan_age (ieee80211_scan_sta.c) immediately does st = ss->ss_priv; IEEE80211_SCAN_TABLE_LOCK(st); TAILQ_... β€” lock acquire and list walk on freed memory. Trigger: ifconfig wlanX destroy (root) racing the per-com inactivity timer. UAF β†’ panic/corruption; root-gated trigger, tiny window, but the pattern is a real TOCTOU on an unlocked function-pointer dispatch (the DF-0587 family β€” same lock-discipline hole, different site).

Verification status

untested β€” the guest has no 802.11 radio device and DF has no virtual 802.11 driver, so a vap cannot be created (ifconfig wlan0 create wlandev … requires a parent radio). The 802.11 pass-1 findings with the same constraint (DF-0640, DF-0587) were left unverified for the same reason. Code walk above is the evidence; fix is to take IEEE80211_LOCK(ic) around the unlocked dispatchers (or make sta_detach clear ss->ss_priv and the readers re-validate under the lock).

Fix verification

not_testable
per-fix-DF-3437

Confirmed kernel references

Detail

Evidence (decisive lines)

['README.md: interleaving diagram + path:line walk', 'ieee80211_node.c:2253 (unlocked callout call), ieee80211_scan.c:586-587 (unlocked ss_ops check-then-call), ieee80211_scan_sta.c:186-199 (sta_detach frees ss_priv, never clears it)']

Verified recommended fix

Take IEEE80211_LOCK(ic) around the ss_ops dispatches in ieee80211_scan.c and clear ss->ss_priv in sta_detach().

Verdict

Not exercised on the guest: the QEMU target has no 802.11 radio and DragonFly has no virtual 802.11 driver, so no vap can be created to drive the scan framework (same constraint that left DF-0640/DF-0587 unverified in pass 1). The defect is established by code walk: ieee80211_scan_timeout (ieee80211_scan.c:582-588, called from the unlocked ieee80211_node_timeout callout, ieee80211_node.c:2243-2256) and scan_iterate/scan_flush (:626-647) dispatch through ss->ss_ops with no com lock, while ieee80211_scan_vdetach (:137-155) under the lock calls scan_detach -> sta_detach (ieee80211_scan_sta.c:186-199) which kfrees ss->ss_priv (the sta_table) without clearing it before ss->ss_ops is NULLed. The check-then-call window lets the timer's scan_age run SCAN_TABLE_LOCK + TAILQ walks on freed memory. Root-gated trigger (ifconfig wlanX destroy) racing a once-per-interval callout; DF-0587 family (lock-discipline hole at a different site).