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

hostap association granted without completed authentication (assoc gate tests node-table existence, and a single auth REQUEST creates the node)

Summary

The ASSOC_REQ handler's only 'not authenticated' gate is 'if (ni == vap->iv_bss)' - i.e. node-table existence. But hostap_auth_shared() creates the node via ieee80211_dup_bss() upon a single seq-1 REQUEST, before the exchange completes, and nothing down to ieee80211_node_join() checks that auth finished. One 30-byte auth REQUEST + one ASSOC_REQ = associated station with AUTHORIZED=0. VERIFIED on stock kernel: control (no node) correctly gets DEAUTH (0xc0); after a lone REQUEST the AP sends ASSOC_RESP (0x10) and assigns associd 0xc001. Found while validating the DF-3323 fix - the patched kernel refused to authorize the station but still handed it an association. Impact: AID-space exhaustion (128 entries, 2 frames per fake station = remote assoc-flood DoS), list-sta presence, power-save eligibility, and EAPOL/PAE pass-through for a fully unauthenticated station. Same predicate exists upstream in FreeBSD. Fix validated (deny assoc when ni==iv_bss OR (authmode SHARED/AUTO and not authorized); 802.1x/WPA untouched): baseline grant replaced by DEAUTH, open-auth regression still associates.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3326 Β· 13 files
FileTypeDescriptionSize
fw4.c β€” 16.5 KB view raw
Makefile β€” 144 B ↓ download
build.sh β€” 87 B view raw
run.sh β€” 111 B view raw
build.log β€” 5.6 KB view raw
run.log β€” 1.5 KB view raw
fix.log β€” 2.2 KB view raw
fixcheck.sh β€” 1.6 KB view raw
fix.diff β€” 925 B view raw
env.txt β€” 198 B view raw
README.md β€” 2.3 KB ↓ raw
VERDICT.md β€” 3.6 KB ↓ raw
verdict.json β€” 2.9 KB view raw

DF-3326 β€” hostap association granted without completed authentication

File: sys/netproto/802_11/wlan/ieee80211_hostap.c Severity: Medium Β· CWE-287/863 Β· confidence: certain Β· REPRODUCED on the stock guest kernel

What

The ASSOC_REQ handler's only authentication gate is node-table existence:

if (ni == vap->iv_bss) {              /* ieee80211_hostap.c:1953 */
        ... deny "sta not authenticated" (DEAUTH, ASSOC_NOT_AUTHED)
}

But a node-table entry is created by a single shared-key auth REQUEST (hostap_auth_shared() :1072-1083 ieee80211_dup_bss()) β€” before the exchange ever completes. So on a shared-key (or auto) AP:

AUTH(shared, seq=1)            # 30 bytes; AP replies with the challenge
ASSOC_REQ                      # never sent the seq-3 response at all
=> ASSOC_RESP, AID assigned    # associated, AUTHORIZED=0

802.11-2016 11.3 requires authentication before association. Impact: AID assignment + list sta entry + power-save queue eligibility + EAPOL (PAE) pass-through for an unauthenticated station; repeated with distinct MACs it exhausts the 128-entry AID space (cheap remote association-flood DoS). (Discovered while validating the DF-3323 fix β€” the patched kernel refused to authorize the station but still handed it an association.)

How to reproduce

sh findings/poc/DF-3326/build.sh
sh findings/poc/DF-3326/run.sh

Success criterion (run.log):

control: fw4.attack=4 with no node   -> AP TX MGMT subtype 0xc0 (DEAUTH)   [correct]
attack:  fw4.attack=1 (request only) -> node AUTHORIZED=0
         fw4.attack=4                -> AP TX MGMT subtype 0x10 (ASSOC_RESP)
                                        associd 0xc001  AUTHORIZED=0        [bug]

Fix validation

fix.diff widens the gate: deny association when ni == iv_bss or (ni_authmode ∈ {SHARED, AUTO} and the port is not authorized) β€” those authmodes complete authentication at the 802.11 layer, so authorized == authenticated; 802.1x/WPA legitimately defer auth until after association. Validated on the rebuilt kernel (#1 21:29, both DF-3323+DF-3326 fixes): attack=1 β†’ attack=4 now yields DEAUTH (0xc0) and associd 0x0; the regression leg (open-authmode vap: open auth attack=6 β†’ AUTHORIZED=1 β†’ assoc) still gets associd 0xc001. See fix.log.

VERDICT.md
↓ download raw

DF-3326 β€” VERDICT

Status: reproduced (baseline, stock INVARIANTS kernel #0 Thu Jul 2 06:02:54 UTC 2026) Β· impact: association (AID, power-save, EAPOL pass-through, assoc-flood DoS) granted without any completed authentication Β· fix: validated (kernel #1 21:29 with DF-3323+DF-3326 fixes).

Root cause (path:line)

  • sys/netproto/802_11/wlan/ieee80211_hostap.c:1953-1962 β€” the ASSOC_REQ "not authenticated" gate tests only ni == vap->iv_bss (node-table existence).
  • sys/netproto/802_11/wlan/ieee80211_hostap.c:1072-1083 β€” hostap_auth_shared() creates the node (ieee80211_dup_bss) upon a seq-1 REQUEST, i.e. before the shared-key exchange completes (or fails). hostap_auth_open() does the same at :947-950 (harmless there: open auth is by definition complete on request).
  • Nothing between node creation and ieee80211_node_join() (:2169, node.c :2524) checks that authentication completed. For shared-key mode ieee80211_node_is_authorized() is only set by a successful seq-3 (:1149) β€” which the assoc path never consults.

The same shape exists for AUTH_AUTO (hostap_auth_shared accepts AUTO nodes at :1023-1024). For 802.1x/WPA, association-before-auth is the designed flow (auth deferred to EAPOL/4-way), so the correct predicate is authmode-dependent (see fix).

Reproduction (fw4 software radio, guest stock kernel)

  • Control: fw4_attack=4 with no node in the table β†’ AP transmits DEAUTH (subtype 0xc0) β€” the intended "not authenticated" refusal exists in the code and is reached when ni == iv_bss.
  • Attack: fw4_attack=1 β€” one 30-byte AUTH(shared, seq1) REQUEST; node created, AUTHORIZED=0. fw4_attack=4 β€” ASSOC_REQ; AP transmits ASSOC_RESP (subtype 0x10), node gains associd 0xc001 while AUTHORIZED=0; ifconfig wlan0 list sta shows AID 1 with no A flag (compare DF-3323 baseline: authorized bypass shows A).
  • Found while validating the DF-3323 fix: on the DF-3323-patched kernel the cleartext seq-3 was correctly dropped, yet the follow-on ASSOC_REQ still returned associd 0xc001 β€” a fully independent second bypass.

Impact

  • Unauthenticated station occupies the AID space (default iv_max_aid 128): 2 frames per fake station β†’ cheap remote association-flood DoS until the inactivity timer reaps them.
  • Appears associated to userland (list sta), eligible for the power-save queue, and β€” because the data path's unauthorized-port filter passes PAE frames (:776) β€” can inject EAPOL into the AP's network stack / bridge.
  • Not authorized: non-PAE data still blocked at :767 (and by DROPUNENC when set) β€” this is the complement of DF-3323, which gained authorization itself.

Fix validation (kernel #1 21:29, DF-3323+DF-3326 fixes applied)

fix.diff widens the gate to ni == vap->iv_bss || ((ni_authmode == SHARED || AUTO) && !authorized).

  • baseline: attack=1 β†’ attack=4 = ASSOC_RESP (0x10), associd 0xc001.
  • patched: attack=1 β†’ attack=4 = DEAUTH (0xc0), associd stays 0x0.
  • regression: open-authmode vap, open auth (attack=6) β†’ AUTHORIZED=1 β†’ ASSOC_REQ β†’ associd 0xc001 (legit flow unbroken). 802.1x/WPA vap types are untouched by the predicate (auth deferred by design).

fix_baseline_reproduced=1, fix_patched_reproduced=0 β†’ fix_status=fixed.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

make -j6 nativekernel rc=0 with DF-3326 fix.diff (+DF-3323 fix); baseline assoc grant (0x10 + associd 0xc001) is gone: DEAUTH 0xc0, associd 0x0; regression: open-auth vap authorizes + associates normally (0xc001).

findings/poc/DF-3326/fix.log (kernel #1 21:29, both fixes)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #1: Thu Sep 10 21:29:25 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Confirmed kernel references

Detail

Exploit chain

1) inject AUTH shared seq1 (node created, not authorized); 2) inject ASSOC_REQ -> ASSOC_RESP + AID; 3) repeat with distinct source MACs to exhaust the 128-entry AID space (remote assoc-flood DoS) or hold assoc state + EAPOL passthrough

Evidence (decisive lines)

run.log: control 'AP TX MGMT subtype 0xc0' (DEAUTH, correct), attack 'AP TX MGMT subtype 0x10' (ASSOC_RESP) + 'associd 0xc001 ... AUTHORIZED=0'; fix.log: patched = 0xc0 + associd 0x0; regression open-auth assoc still 0xc001

PoC changes

same fw4 harness as DF-3323; fw4.c extended with generic mgmt-subtype TX capture (0xc0/0x10 distinction), attack=4 fallback to iv_bss for the no-node control, attack=6 open-auth regression leg

Verified recommended fix

Widen the assoc gate: deny when ni==iv_bss OR (ni_authmode in {SHARED, AUTO} AND port not authorized); see fix.diff

Verdict

hostap_recv_mgmt's ASSOC_REQ gate (:1953) tests only ni == iv_bss, but hostap_auth_shared creates the node on a single auth REQUEST (:1072 dup_bss) before the exchange completes. One 30-byte REQUEST + one ASSOC_REQ = associated station (associd 0xc001) with AUTHORIZED=0, reproduced on the stock kernel; the no-node control correctly gets DEAUTH (0xc0). Found while validating the DF-3323 fix, where the patched kernel still handed out the association.