SME: Handle PMF association comeback when not handled in driver

In associations using PMF (IEEE 802.11w/MFP), the infrastructure
implements SA teardown protection by rejecting an (Re)Association
Request frame 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.

This association comeback process has commonly been implemented in the
driver, e.g., within mac80211 in case of the Linux drivers that use SME
in userspace. However, there are drivers that do not implement this
functionality. Extended wpa_supplicant to cover such cases as well.

The current implementation interprets this association error as a true
error, and will either add the BSS to the list of ignored BSSIDs, or
continue to try other BSSes. This can cause wpa_supplicant to back off
trying to reconnect for progressively longer intervals, depending on the
infrastructure's configured comeback timeout.

Allow wpa_supplicant to interpret the error, searching for the Timeout
Interval element in the (Re)Association Response frame 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.

This does not change behavior for drivers that implement association
comeback timer internally since they do not report the temporary
association rejection status code to user space.

Signed-off-by: Harry Bock <hbock@zebra.com>
This commit is contained in:
Harry Bock 2024-01-10 14:09:09 -05:00 committed by Jouni Malinen
parent a86709b52e
commit 33179cd291
2 changed files with 102 additions and 7 deletions

View file

@ -2205,6 +2205,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) {
@ -2711,15 +2714,109 @@ 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;
if (!wpa_s->current_bss || !wpa_s->current_ssid) {
wpa_msg(wpa_s, MSG_DEBUG,
"SME: Comeback timeout expired; SSID/BSSID cleared; ignoring");
return;
}
wpa_msg(wpa_s, MSG_DEBUG,
"SME: Comeback timeout expired; retry associating with "
MACSTR "; mode=%d auth_type=%u",
MAC2STR(wpa_s->current_bss->bssid),
wpa_s->current_ssid->mode,
wpa_s->sme.assoc_auth_type);
/* Authentication 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 bool sme_try_assoc_comeback(struct wpa_supplicant *wpa_s,
union wpa_event_data *data)
{
struct ieee802_11_elems elems;
u32 timeout_interval;
unsigned long comeback_usec;
if (ieee802_11_parse_elems(data->assoc_reject.resp_ies,
data->assoc_reject.resp_ies_len,
&elems, 0) == ParseFailed) {
wpa_msg(wpa_s, MSG_INFO,
"SME: Temporary assoc reject: failed to parse (Re)Association Response frame elements");
return false;
}
if (!elems.timeout_int) {
wpa_msg(wpa_s, MSG_INFO,
"SME: Temporary assoc reject: missing timeout interval IE");
return false;
}
if (elems.timeout_int[0] != WLAN_TIMEOUT_ASSOC_COMEBACK) {
wpa_msg(wpa_s, MSG_INFO,
"SME: Temporary assoc reject: missing association comeback time");
return false;
}
timeout_interval = WPA_GET_LE32(&elems.timeout_int[1]);
if (timeout_interval > 60000) {
/* This is unprotected information and there is no point in
* getting stuck waiting for very long duration based on it */
wpa_msg(wpa_s, MSG_DEBUG,
"SME: Ignore overly long association comeback interval: %u TUs",
timeout_interval);
return false;
}
wpa_msg(wpa_s, MSG_DEBUG, "SME: Association comeback interval: %u TUs",
timeout_interval);
comeback_usec = timeout_interval * 1024;
eloop_register_timeout(comeback_usec / 1000000, comeback_usec % 1000000,
sme_assoc_comeback_timer, wpa_s, NULL);
return true;
}
void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
union wpa_event_data *data,
const u8 **link_bssids)
{
const u8 *bssid;
if (wpa_s->valid_links)
bssid = wpa_s->ap_mld_addr;
else
bssid = wpa_s->pending_bssid;
wpa_dbg(wpa_s, MSG_DEBUG, "SME: Association with " MACSTR " failed: "
"status code %d", MAC2STR(wpa_s->pending_bssid),
data->assoc_reject.status_code);
eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
/* Authentication phase has been completed at this point. Check whether
* the AP rejected association temporarily due to still holding a
* security associationis with us (MFP). If so, we must wait for the
* AP's association comeback timeout period before associating again. */
if (data->assoc_reject.status_code ==
WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
wpa_msg(wpa_s, MSG_DEBUG,
"SME: Temporary association reject from BSS " MACSTR,
MAC2STR(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 &&
@ -2731,12 +2828,6 @@ void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
if (wpa_s->current_bss) {
struct wpa_bss *bss = wpa_s->current_bss;
struct wpa_ssid *ssid = wpa_s->current_ssid;
const u8 *bssid;
if (wpa_s->valid_links)
bssid = wpa_s->ap_mld_addr;
else
bssid = wpa_s->pending_bssid;
wpa_drv_deauthenticate(wpa_s, bssid,
WLAN_REASON_DEAUTH_LEAVING);
@ -2847,8 +2938,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);
}
@ -2881,6 +2974,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);
}

View file

@ -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 */