[PATCH] SME: handle 802.11w association comeback

Harry Bock hbock at zebra.com
Wed Jan 10 11:31:17 PST 2024


In 802.11w/MFP networks, the infrastructure implements SA teardown
protection by rejecting an Association Request from an
already-associated client.  The AP responds with error 30 (Association
request rejected temporarily) to instruct the (potentially spoofing)
client to back off, while it issues an SA-Query procedure to the
already-associated client.  If the client can respond to it within the
back-off period, it considers the new association to be a spoof
attempt.

However, there are cases where a legitimate client might need to
handle this error response - consider if the STA has deauthenticated,
but the AP cannot hear it (out of range).  If the MFP STA has deleted
its keys, it cannot respond to the SA-Query procedure.

The current implementation interprets this association error as a true
error, and will either add the BSS to the blacklist, or continue to
try other BSSes - all of which will respond with the same error.  This
can cause the supplicant to back off trying to reconnect for
progressively longer intervals, depending on the infrastructure's
configured comeback timeout.

This patch allows the supplicant to interpret the error, searching for
the Timeout Interval IE in the Association response and starting a
timer in the SME layer to re-associate after the timeout.  This can be
a long delay (1-4 seconds in my experience), but it is likely much
shorter than bouncing between nearby BSSes.

Signed-off-by: Harry Bock <hbock at zebra.com>
---
 src/common/ieee802_11_defs.h      |  6 +++
 wpa_supplicant/sme.c              | 67 ++++++++++++++++++++++++++++++-
 wpa_supplicant/wpa_supplicant_i.h |  1 +
 3 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 12137dcf4..6192f2396 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -3007,6 +3007,12 @@ struct ieee80211_neighbor_ap_info {
 	u8 data[0];
 } STRUCT_PACKED;
 
+struct ieee80211_timeout_interval {
+	u8 timeout_interval_type;
+	u32 timeout_interval_value;
+} STRUCT_PACKED;
+
+
 #ifdef _MSC_VER
 #pragma pack(pop)
 #endif /* _MSC_VER */
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index c791e116b..82ff82281 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -39,6 +39,8 @@
 static void sme_auth_timer(void *eloop_ctx, void *timeout_ctx);
 static void sme_assoc_timer(void *eloop_ctx, void *timeout_ctx);
 static void sme_obss_scan_timeout(void *eloop_ctx, void *timeout_ctx);
+static void sme_assoc_comeback_timer(void *eloop_ctx, void *timeout_ctx);
+
 static void sme_stop_sa_query(struct wpa_supplicant *wpa_s);
 
 
@@ -2195,6 +2197,9 @@ void sme_associate(struct wpa_supplicant *wpa_s, enum wpas_mode mode,
 
 	os_memset(&params, 0, sizeof(params));
 
+	/* save auth type, in case we need to retry after comeback timer. */
+	wpa_s->sme.assoc_auth_type = auth_type;
+
 #ifdef CONFIG_FILS
 	if (auth_type == WLAN_AUTH_FILS_SK ||
 	    auth_type == WLAN_AUTH_FILS_SK_PFS) {
@@ -2701,6 +2706,47 @@ static void sme_deauth(struct wpa_supplicant *wpa_s, const u8 **link_bssids)
 }
 
 
+static void sme_assoc_comeback_timer(void *eloop_ctx, void *timeout_ctx)
+{
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+	wpa_msg(wpa_s, MSG_DEBUG, "SME: Comeback timeout expired; retry associating to " MACSTR "; mode=%d auth_type=%u",
+			MAC2STR(wpa_s->current_bss->bssid),
+			wpa_s->current_ssid->mode,
+			wpa_s->sme.assoc_auth_type);
+
+	/* auth state was completed already; just try association again. */
+	sme_associate(wpa_s, wpa_s->current_ssid->mode, wpa_s->current_bss->bssid, wpa_s->sme.assoc_auth_type);
+}
+
+static int sme_try_assoc_comeback(struct wpa_supplicant *wpa_s, 
+					union wpa_event_data *data)
+{
+	int try_comeback = 0;
+	struct ieee802_11_elems elems;
+	if (ParseOK == ieee802_11_parse_elems(data->assoc_reject.resp_ies, data->assoc_reject.resp_ies_len, &elems, 0)) {
+		if (elems.timeout_int) {
+			struct ieee80211_timeout_interval* timeout = (struct ieee80211_timeout_interval*)elems.timeout_int;
+			if (timeout->timeout_interval_type == WLAN_TIMEOUT_ASSOC_COMEBACK) {
+				wpa_msg(wpa_s, MSG_DEBUG, "SME: Association comeback interval: %u TUs",
+					timeout->timeout_interval_value);
+
+				u64 comeback_usec = ((u64)timeout->timeout_interval_value * 1024uLL);
+				eloop_register_timeout((unsigned int)(comeback_usec / 1000000uLL),
+									(unsigned int)(comeback_usec % 1000000uLL),
+									sme_assoc_comeback_timer, wpa_s, NULL);
+				try_comeback = 1;
+			}
+		/* no timeout interval IE; can't come back. */
+		} else {
+			wpa_msg(wpa_s, MSG_ERROR, "SME: Temporary assoc reject: missing timeout interval IE");
+		}
+	} else {
+		wpa_msg(wpa_s, MSG_ERROR, "SME: Temporary assoc reject: fail to parse association resp IEs");
+	}
+
+	return try_comeback;
+}
+
 void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
 			    union wpa_event_data *data,
 			    const u8 **link_bssids)
@@ -2710,6 +2756,22 @@ void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
 		data->assoc_reject.status_code);
 
 	eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
+	eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
+
+	/* Auth phase completed, but AP thinks our previous authentication is still valid (MFP).
+	 * Meaning our auth states are out of sync.
+	 * Perhaps we went out of range and/or deauthenticated, but AP didn't hear our deauth.
+	 * AP rejects us temporarily, issues SA-Query challenge (which we can't complete anymore).
+	 * We must wait for the AP's association comeback timeout period before associating
+	 * again.
+	 */
+	if (WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY == data->assoc_reject.status_code) {
+		wpa_msg(wpa_s, MSG_DEBUG, "SME: Temporary assoc reject from BSS " MACSTR, MAC2STR(wpa_s->current_bss->bssid));
+		if (sme_try_assoc_comeback(wpa_s, data)) {
+			/* break out early; comeback error is not a failure. */
+			return;
+		}
+	}
 
 #ifdef CONFIG_SAE
 	if (wpa_s->sme.sae_pmksa_caching && wpa_s->current_ssid &&
@@ -2837,8 +2899,10 @@ static void sme_assoc_timer(void *eloop_ctx, void *timeout_ctx)
 void sme_state_changed(struct wpa_supplicant *wpa_s)
 {
 	/* Make sure timers are cleaned up appropriately. */
-	if (wpa_s->wpa_state != WPA_ASSOCIATING)
+	if (wpa_s->wpa_state != WPA_ASSOCIATING) {
 		eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
+		eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
+	}
 	if (wpa_s->wpa_state != WPA_AUTHENTICATING)
 		eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL);
 }
@@ -2871,6 +2935,7 @@ void sme_deinit(struct wpa_supplicant *wpa_s)
 	eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
 	eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL);
 	eloop_cancel_timeout(sme_obss_scan_timeout, wpa_s, NULL);
+	eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
 }
 
 
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 44e4416b1..a5c1d4ca6 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1033,6 +1033,7 @@ struct wpa_supplicant {
 		bool ext_ml_auth;
 		int *sae_rejected_groups;
 #endif /* CONFIG_SAE */
+		u16 assoc_auth_type;
 	} sme;
 #endif /* CONFIG_SME */
 
-- 
2.34.1


- CONFIDENTIAL-

This email and any files transmitted with it are confidential, and may also be legally privileged.  If you are not the intended recipient, you may not review, use, copy, or distribute this message. If you receive this email in error, please notify the sender immediately by reply email and then delete this email.



More information about the Hostap mailing list