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

ath_vap_delete second TX drain races live taskqueue workers before kfree(avp) β€” author-acknowledged use-after-free window on vap teardown

Summary

ath_vap_delete disables interrupts but never blocks or drains the driver taskqueue - unlike ath_reset and ath_key_update_begin which block the same queue for exactly this hazard. ieee80211_vap_detach queues a disassoc frame after the first drain (acknowledged by the in-tree 'XXX Danger Will Robinson' comment); a concurrently running/queued sc_txtask/sc_txqtask can be scheduling that frame sw-to-hw while the second ath_draintxq runs, holding buffers whose completion callbacks and av_mcastq linkage reference the avp kfreed at :1860. Use-after-free of struct ath_vap on SMP under TX load during vap destroy - panic at minimum, slab-reuse-dependent for worse. Entry admin-triggered, timing-dependent; race speculative but the missing synchronization is objectively absent and upstream-acknowledged. Fix: taskqueue_block + drains between second drain and kfree.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3618 Β· 6 files
FileTypeDescriptionSize
README.md β€” 3.2 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
env.txt β€” 388 B view raw
fix.diff β€” 900 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.4 KB view raw

DF-3618 β€” ath_vap_delete: racy second TX drain around ieee80211_vap_detach β†’ freed-vap use

The defect (author-acknowledged in-tree)

sys/dev/netif/ath/ath/if_ath.c:1766-1884 (ath_vap_delete):

    ieee80211_vap_detach(vap);            /* 1793 */

    /*
     * XXX Danger Will Robinson! Danger!
     * Because ieee80211_vap_detach() can queue a frame (the station
     * diassociate message?) after we've drained the TXQ and flushed
     * the software TXQ, we will end up with a frame queued to a node
     * whose vap is about to be freed.
     * ... This may be racy - the ath task may be running and the
     * packet may be being scheduled between sw->hw txq. Tsk.
     */
    ath_draintxq(sc, ATH_RESET_DEFAULT);  /* 1812 */
    ...
    kfree(avp, M_80211_VAP);              /* 1860 */

Sequence that leaves a use-after-free window:

  1. 1785: interrupts disabled (ath_hal_intrset(ah, 0)) β€” but taskqueue tasks already enqueued/running are NOT stopped: nothing here calls taskqueue_block()/taskqueue_drain() (contrast ath_reset:2889/3021 and ath_key_update_begin:3542 which do block the tq).
  2. 1793: ieee80211_vap_detach() β€” per the in-tree comment this queues a frame (disassoc) onto TX machinery after the first drain.
  3. That frame is scheduled sw→hw by the still-live sc_txtask/sc_txqtask worker (enqueued by earlier interrupts, or by ath_tx_kick from another thread) concurrently with the second ath_draintxq at 1812 — the exact interleave the comment itself calls racy.
  4. 1835: ath_tx_draintxq(sc, &avp->av_mcastq) drains the vap's software mcast queue β€” a field INSIDE avp β€” without any synchronization against the task worker that can, in the same window, be manipulating av_mcastq-queued buffers (ath_tx_processq β†’ completion callbacks that call back into vap methods).
  5. 1860: kfree(avp) β€” any buffer whose completion/callback still references the vap (bf_comp handlers, mcastq linkage, ni->ni_vap) now touches freed memory.

Concurrent writers that are not excluded: ath_intr ISRs already past ath_hal_intrpend (same argument as DF-3617 β€” intrset(0) at 1785 does not wait), and the taskqueue thread executing ath_tx_proc*/ath_txq_sched_tasklet. ath_transmit itself is serialized by net80211 comlock against vap delete, but the driver's deferred TX machinery is not.

Preconditions

Atheros NIC, active TX (or vap-detach-queued frames), SMP, admin destroys a vap (ifconfig destroy / cloning teardown). The race is timing-dependent β€” speculative on triggerability, author-acknowledged in the source comment.

Impact if hit

Use of freed struct ath_vap (mcastq linkage, completion callbacks) β†’ kernel UAF read/write β†’ panic at minimum; slab-reuse dependent for anything worse.

Why inconclusive here

Guest has no ath hardware (env.txt) β€” ath_vap_delete on an ath vap cannot be driven; the race additionally needs SMP timing under TX load.

Fix

fix.diff is a targeted mitigation: block+drain the driver taskqueue tasks between the second drain and the kfree, so no deferred task can still hold vap state when it is freed. (Upstream later reworked this whole path; the minimal drain closes the specific window the comment identifies.)

VERDICT.md
↓ download raw

DF-3618 β€” VERDICT

Status: inconclusive (author-acknowledged race; UAF-class; needs ath hardware + SMP timing to fire; not executable on this guest)

Reproduced?

No β€” and it cannot be reproduced here: ath_vap_delete requires an attached ath vap; the guest has no Atheros NIC (env.txt). Beyond hardware, the window needs an in-flight/queued taskqueue worker racing the second ath_draintxq under TX load on SMP.

Why this is filed anyway (pass-2 lifetime analysis)

The defect is acknowledged in the source itself (if_ath.c:1796-1810 "XXX Danger Will Robinson! Danger! ... This may be racy - the ath task may be running and the packet may be being scheduled between sw->hw txq."). The audit-independent facts that make it a real lifetime bug and not just a stale comment:

  • Interrupts are disabled at 1785 but the driver never blocks/drains its own taskqueue in this path, unlike ath_reset (2889 + 3021) and ath_key_update_begin (3542) which deliberately do.
  • A task already executing ath_tx_processq/ath_txq_sched_tasklet holds buffers whose completion path (bf_comp callbacks, ieee80211_tx_complete β†’ ni->ni_vap) and whose queue linkage (avp->av_mcastq, drained at 1835 without exclusion) reference memory kfreed at 1860.
  • ieee80211_vap_detach (1793) provably queues a disassoc frame after the first drain β€” that frame is precisely what the second drain races.

Class: CWE-412/USE-AFTER-FREE window; exploitability speculative (needs slab grooming during a destroy under load β€” admin-triggered entry, so privilege bar is low value anyway).

Fix validation

Not possible on this guest (no hardware). fix.diff is the minimal taskqueue block+drain mitigation; the real fix is the upstream-style rework of vap teardown ordering.

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

fix.diff (mitigation) applies clean via git apply --check; runtime validation impossible without ath hardware

findings/poc/DF-3618/fix.diff
↓ fix.diffper-fix-DF-3618

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md full trace with line cites; in-tree comment if_ath.c:1796-1810 acknowledging the race; env.txt (no ath hw); fix.diff git-apply clean

PoC changes

n/a - no seeded PoC; analysis-only pack

Verified recommended fix

taskqueue_block + taskqueue_drain of sc_txtask/sc_txqtask/sc_rxtask between the second ath_draintxq and kfree(avp), mirroring ath_reset's discipline

Verdict

ath_vap_delete (if_ath.c:1766-1884) disables interrupts at 1785 but never blocks or drains the driver taskqueue, unlike ath_reset (2889/3021) and ath_key_update_begin (3542) which block the same queue for exactly this class of hazard. ieee80211_vap_detach (1793) queues a disassoc frame after the first drain (acknowledged by the in-tree 'XXX Danger Will Robinson' comment at 1796-1810); a concurrently-running/queued sc_txtask/sc_txqtask can be scheduling that frame sw-to-hw while the second ath_draintxq (1812) runs, and can hold buffers whose completion callbacks and mcastq linkage reference the avp that is kfreed at 1860. UAF-class window, speculative triggerability (SMP + TX load + vap destroy timing), admin-triggered entry. Not executable on the audit guest: no Atheros NIC (env.txt), and the race needs live hardware TX. Mitigation diff (taskqueue block+drain before kfree) applies clean.