[PATCH v3 1/2] SME: Fall back from PMKSA caching if the AP deauthenticates

Louis Kotze loukot at gmail.com
Tue Jul 28 04:46:00 PDT 2026


When an association attempt uses SAE PMKSA caching, an AP that does not
accept the cached PMKSA is expected to respond to the (Re)Association
Request frame with an error status code, which makes wpa_supplicant
drop the PMKSA cache entry and fall back to a full SAE authentication.
However, some APs reject such an association attempt by deauthenticating
the STA with reason code 9 (STA_REQ_ASSOC_WITHOUT_AUTH) instead of
sending an Association Response frame. That path did not drop the PMKSA
cache entry, so every subsequent connection attempt found the same entry
again, retried PMKSA caching, and was deauthenticated again. The
connection kept failing in a loop without ever falling back to SAE
authentication.

Observed with a TP-Link Deco BE65 AP MLD. The Open System authentication
exchange that precedes the PMKSA caching attempt completes with status
code 0, no Association Response frame follows the Association Request
frame at all, and the AP deauthenticates with reason code 9 about 0.6
seconds later. The AP does not appear to move the STA into authenticated
state based on that Authentication frame exchange.

Drop the PMKSA cache entry for that AP when it deauthenticates the STA
with reason code 9 while an association attempt that used PMKSA caching
is pending, so that the next attempt falls back to a full SAE
authentication.

sme_event_assoc_reject() already does this for the case where the AP
responds with an error status code. The new handler mirrors that cache
handling, but not the reconnection that follows it there: the AP has
already deauthenticated in this case, so wpas_event_disconnect() runs
immediately afterwards and drives the reconnection.

Unlike an association reject, a Deauthentication frame can arrive at any
point, so the handler matches only the sequence described above: a frame
from the AP, not a locally generated event, with reason code 9, while
the state machine is in WPA_ASSOCIATING. The PMKSA cache flush is
limited to the address of the AP that sent the frame and is skipped
entirely if that address is not known, so that an unprotected frame from
a single AP cannot drop the PMKSA cache entries of the whole ESS.

Signed-off-by: Louis Kotze <loukot at gmail.com>
---
 wpa_supplicant/events.c |  3 +++
 wpa_supplicant/sme.c    | 35 +++++++++++++++++++++++++++++++++++
 wpa_supplicant/sme.h    |  7 +++++++
 3 files changed, 45 insertions(+)

diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index 2b6eb2a88..bcbe55ec3 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -5892,6 +5892,9 @@ static void wpas_event_deauth(struct wpa_supplicant *wpa_s,
 
 	wpa_reset_ft_completed(wpa_s->wpa);
 
+	if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)
+		sme_event_deauth(wpa_s, info);
+
 	wpas_event_disconnect(wpa_s, addr, reason_code,
 			      locally_generated, ie, ie_len, 1);
 }
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index 253716609..1dfb5fae1 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -4985,6 +4985,41 @@ void sme_event_assoc_timed_out(struct wpa_supplicant *wpa_s,
 }
 
 
+void sme_event_deauth(struct wpa_supplicant *wpa_s,
+		      struct deauth_info *info)
+{
+#ifdef CONFIG_SAE
+	const u8 *aa;
+
+	/* Some APs reject a (Re)Association Request frame that tries to use
+	 * PMKSA caching by deauthenticating the STA with reason code 9
+	 * (STA_REQ_ASSOC_WITHOUT_AUTH) instead of responding to it with an
+	 * error status code. Drop the PMKSA cache entry for that AP in that
+	 * case so that the next attempt uses a full SAE authentication
+	 * instead of finding the same PMKSA cache entry again and repeating
+	 * the same failure. */
+	if (!wpa_s->sme.sae_pmksa_caching || !info || info->locally_generated ||
+	    info->reason_code != WLAN_REASON_STA_REQ_ASSOC_WITHOUT_AUTH ||
+	    wpa_s->wpa_state != WPA_ASSOCIATING || !wpa_s->current_ssid ||
+	    !wpa_key_mgmt_sae(wpa_s->current_ssid->key_mgmt))
+		return;
+
+	if (wpa_s->valid_links)
+		aa = wpa_s->ap_mld_addr;
+	else if (wpa_s->current_bss)
+		aa = wpa_s->current_bss->bssid;
+	else
+		return; /* Do not flush the PMKSA cache entries of the full ESS
+			 * based on an unprotected frame from a single AP. */
+
+	wpa_dbg(wpa_s, MSG_DEBUG,
+		"SME: PMKSA caching attempt rejected with deauthentication - drop PMKSA cache entry");
+	wpa_sm_aborted_cached(wpa_s->wpa);
+	wpa_sm_pmksa_cache_flush_addr(wpa_s->wpa, wpa_s->current_ssid, aa);
+#endif /* CONFIG_SAE */
+}
+
+
 void sme_event_disassoc(struct wpa_supplicant *wpa_s,
 			struct disassoc_info *info)
 {
diff --git a/wpa_supplicant/sme.h b/wpa_supplicant/sme.h
index a4521dddb..3466f8aec 100644
--- a/wpa_supplicant/sme.h
+++ b/wpa_supplicant/sme.h
@@ -25,6 +25,8 @@ void sme_event_auth_timed_out(struct wpa_supplicant *wpa_s,
 			      union wpa_event_data *data);
 void sme_event_assoc_timed_out(struct wpa_supplicant *wpa_s,
 			       union wpa_event_data *data);
+void sme_event_deauth(struct wpa_supplicant *wpa_s,
+		      struct deauth_info *info);
 void sme_event_disassoc(struct wpa_supplicant *wpa_s,
 			struct disassoc_info *info);
 void sme_event_unprot_disconnect(struct wpa_supplicant *wpa_s, const u8 *sa,
@@ -80,6 +82,11 @@ static inline void sme_event_assoc_timed_out(struct wpa_supplicant *wpa_s,
 {
 }
 
+static inline void sme_event_deauth(struct wpa_supplicant *wpa_s,
+				    struct deauth_info *info)
+{
+}
+
 static inline void sme_event_disassoc(struct wpa_supplicant *wpa_s,
 				      struct disassoc_info *info)
 {
-- 
2.55.0




More information about the Hostap mailing list