[PATCH v2 1/3] PR: Ignore duplicate and out-of-order PASN authentication frames
Emily Xia
xiayucheng at google.com
Fri Oct 30 02:23:13 PDT 2026
During Proximity Ranging PASN negotiation, off-channel dwell latency or
lost 802.11 ACKs can cause duplicate or out-of-order PASN Authentication
frames (M1, M2, or M3) to arrive after the state machine has already
advanced, leading to corrupted keys, wrapper overwrites, or premature
session aborts:
1. In pr_pasn_handle_auth_1(), set pasn->trans_seq to
WLAN_AUTH_TR_SEQ_PASN_AUTH2 after sending M2 and ignore duplicate M1
frames arriving while the responder has already advanced to Auth2,
preventing re-initialization of the responder's ephemeral key/PTK.
Also ignore unexpected Auth1 frames arriving on an initiator.
2. In pr_pasn_handle_auth_2(), ignore duplicate Auth2 frames when
trans_seq has already advanced past Auth1 before modifying
ranging_wrapper, and treat non-fatal return codes (-2 and -3) from
wpa_pasn_auth_rx() as ignored frames (ret = 0) without resetting
pasn_role, matching upstream pasn_supplicant.c. Also ignore unexpected
Auth2 frames on role mismatch instead of tearing down the session.
3. In pr_pasn_handle_auth_3(), ignore duplicate Auth3 frames when
trans_seq >= WLAN_AUTH_TR_SEQ_PASN_AUTH3 before modifying wrappers
or re-triggering ranging, and advance trans_seq to Auth3 upon
successful verification. Also ignore unexpected Auth3 frames on role
mismatch instead of tearing down the session.
4. Implement pr_reset_pasn_state() to reset dev->pasn_role to
PR_ROLE_IDLE and call wpa_pasn_reset() when clearing ranging
parameters (wpas_pr_clear_ranging_params()) and when scheduling
negotiation for both Initiator and Responder roles
(wpas_pr_initiate_pasn_auth()), ensuring subsequent sessions with the
same peer start fresh without mistaking new Auth1 frames for
duplicates.
5. In pr_pasn_auth_rx() and pr_pasn_auth_tx_status(), ignore frames from
unknown peers, frames addressed to other devices, non-PASN auth
algorithms, and TX status events without an active session instead of
aborting active Proximity Ranging.
Signed-off-by: Emily Xia <xiayucheng at google.com>
Signed-off-by: Sunil Ravi <sunilravi at google.com>
---
Changes in v2:
- Resend as part of the series.
src/common/proximity_ranging.c | 105 ++++++++++++++++++++++++++++-----
src/common/proximity_ranging.h | 1 +
wpa_supplicant/pr_supplicant.c | 7 +++
3 files changed, 99 insertions(+), 14 deletions(-)
diff --git a/src/common/proximity_ranging.c b/src/common/proximity_ranging.c
index b614693fa..e8b8d2078 100644
--- a/src/common/proximity_ranging.c
+++ b/src/common/proximity_ranging.c
@@ -132,6 +132,35 @@ void pr_flush(struct pr_data *pr)
#endif /* CONFIG_PASN */
}
+
+void pr_reset_pasn_state(struct pr_data *pr, const u8 *addr)
+{
+ struct pr_device *dev;
+
+ if (!pr)
+ return;
+
+ if (addr) {
+ dev = pr_get_device(pr, addr);
+ if (dev) {
+ dev->pasn_role = PR_ROLE_IDLE;
+#ifdef CONFIG_PASN
+ if (dev->pasn)
+ wpa_pasn_reset(dev->pasn);
+#endif /* CONFIG_PASN */
+ }
+ return;
+ }
+
+ dl_list_for_each(dev, &pr->devices, struct pr_device, list) {
+ dev->pasn_role = PR_ROLE_IDLE;
+#ifdef CONFIG_PASN
+ if (dev->pasn)
+ wpa_pasn_reset(dev->pasn);
+#endif /* CONFIG_PASN */
+ }
+}
+
void pr_deinit(struct pr_data *pr)
{
struct pr_device *dev, *prev;
@@ -2116,9 +2145,10 @@ int pr_pasn_auth_tx_status(struct pr_data *pr, const u8 *data, size_t data_len,
dev = pr_get_device(pr, mgmt->da);
if (!dev || !dev->pasn) {
- wpa_printf(MSG_INFO, "PR PASN: Peer not found " MACSTR,
+ wpa_printf(MSG_INFO,
+ "PR PASN: Peer not found " MACSTR ", ignore the frame",
MAC2STR(mgmt->da));
- return -1;
+ return 0;
}
pasn = dev->pasn;
@@ -2500,6 +2530,20 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
pasn_groups[0] = 19;
}
+ if (dev->pasn_role == PR_ROLE_PASN_INITIATOR) {
+ wpa_printf(MSG_INFO,
+ "PR PASN: Ignoring unexpected Auth1 on initiator");
+ return 0;
+ }
+
+ if (dev->pasn_role == PR_ROLE_PASN_RESPONDER && dev->pasn &&
+ dev->pasn->trans_seq >= WLAN_AUTH_TR_SEQ_PASN_AUTH2) {
+ wpa_printf(MSG_INFO,
+ "PR PASN: Ignoring duplicate Auth1 (trans_seq=%u)",
+ dev->pasn->trans_seq);
+ return 0;
+ }
+
if (pr_process_pasn_ranging_wrapper(pr, dev, mgmt, len, 1)) {
wpa_printf(MSG_INFO,
"PR PASN: Failed to handle Auth1 action wrapper");
@@ -2541,6 +2585,7 @@ static int pr_pasn_handle_auth_1(struct pr_data *pr, struct pr_device *dev,
wpa_printf(MSG_INFO, "PR PASN: Handle Auth1 failed");
goto fail;
}
+ dev->pasn->trans_seq = WLAN_AUTH_TR_SEQ_PASN_AUTH2;
if (!(dev->protocol_type & PR_EDCA_BASED_RANGING) &&
pr->cfg->set_keys &&
@@ -2573,13 +2618,21 @@ static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev,
if (dev->pasn_role != PR_ROLE_PASN_INITIATOR) {
wpa_printf(MSG_INFO,
- "PR PASN: Auth2 not expected on responder");
- return -1;
+ "PR PASN: Ignoring unexpected Auth2 (role=%d)",
+ dev->pasn_role);
+ return 0;
}
if (!dev->pasn)
return -1;
+ if (dev->pasn->trans_seq != WLAN_AUTH_TR_SEQ_PASN_AUTH1) {
+ wpa_printf(MSG_INFO,
+ "PR PASN: Ignoring duplicate Auth2 (trans_seq=%u)",
+ dev->pasn->trans_seq);
+ return 0;
+ }
+
if (pr_process_pasn_ranging_wrapper(pr, dev, mgmt, len, 2)) {
wpa_printf(MSG_INFO,
"PR PASN: Failed to handle Auth2 action wrapper");
@@ -2588,13 +2641,26 @@ static int pr_pasn_handle_auth_2(struct pr_data *pr, struct pr_device *dev,
pasn_set_extra_ies(dev->pasn, wpabuf_head_u8(dev->ranging_wrapper),
wpabuf_len(dev->ranging_wrapper));
- if (wpa_pasn_auth_rx(dev->pasn, (const u8 *) mgmt, len,
- &pasn_data) < 0) {
+ ret = wpa_pasn_auth_rx(dev->pasn, (const u8 *) mgmt, len, &pasn_data);
+ if (ret == -2 || ret == -3) {
+ wpa_printf(MSG_INFO,
+ "PR PASN: Ignoring invalid or out-of-order Auth2 (ret=%d)",
+ ret);
+ ret = 0;
+ goto fail;
+ }
+ if (ret < 0) {
wpa_printf(MSG_INFO, "PR PASN: wpa_pasn_auth_rx() failed");
dev->pasn_role = PR_ROLE_IDLE;
goto fail;
}
+ /*
+ * wpa_pasn_auth_rx() already advances dev->pasn->trans_seq to
+ * WLAN_AUTH_TR_SEQ_PASN_AUTH2 in wpas_parse_pasn_frame() and to
+ * WLAN_AUTH_TR_SEQ_PASN_AUTH3 in wpas_pasn_build_auth_3(), which
+ * wpa_pasn_auth_tx_status() requires when M3 TX status arrives.
+ */
if (!(dev->protocol_type & PR_EDCA_BASED_RANGING) &&
pr->cfg->set_keys &&
pr->cfg->set_keys(pr->cfg->cb_ctx, pr->cfg->dev_addr,
@@ -2619,13 +2685,21 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
if (dev->pasn_role != PR_ROLE_PASN_RESPONDER) {
wpa_printf(MSG_INFO,
- "PR PASN: Auth3 not expected on initiator");
- return -1;
+ "PR PASN: Ignoring unexpected Auth3 (role=%d)",
+ dev->pasn_role);
+ return 0;
}
if (!dev->pasn)
return -1;
+ if (dev->pasn->trans_seq >= WLAN_AUTH_TR_SEQ_PASN_AUTH3) {
+ wpa_printf(MSG_INFO,
+ "PR PASN: Ignoring duplicate Auth3 (trans_seq=%u)",
+ dev->pasn->trans_seq);
+ return 0;
+ }
+
if (pr_process_pasn_ranging_wrapper_result(pr, dev, mgmt, len)) {
wpa_printf(MSG_INFO,
"PR PASN: Failed to handle Auth3 action wrapper");
@@ -2637,6 +2711,7 @@ static int pr_pasn_handle_auth_3(struct pr_data *pr, struct pr_device *dev,
wpa_printf(MSG_INFO, "PR PASN: Failed to handle Auth3");
goto fail;
}
+ dev->pasn->trans_seq = WLAN_AUTH_TR_SEQ_PASN_AUTH3;
if (pr->cfg->pasn_result)
pr->cfg->pasn_result(pr->cfg->cb_ctx, dev->ranging_role,
@@ -2688,14 +2763,16 @@ int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
dev = pr_get_device(pr, mgmt->sa);
if (!dev) {
- wpa_printf(MSG_INFO, "PR: Peer not found " MACSTR,
+ wpa_printf(MSG_INFO,
+ "PR PASN: Peer not found " MACSTR ", ignore the frame",
MAC2STR(mgmt->sa));
- return -1;
+ return 0;
}
if (!ether_addr_equal(mgmt->da, pr->cfg->dev_addr)) {
- wpa_printf(MSG_INFO, "PR PASN: Not our frame");
- return -1;
+ wpa_printf(MSG_INFO,
+ "PR PASN: Not our frame, ignore the frame");
+ return 0;
}
if (len < offsetof(struct ieee80211_mgmt, u.auth.variable))
@@ -2704,9 +2781,9 @@ int pr_pasn_auth_rx(struct pr_data *pr, const struct ieee80211_mgmt *mgmt,
auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
if (auth_alg != WLAN_AUTH_PASN) {
wpa_printf(MSG_INFO,
- "PR: Unexpected Authentication frame, auth_alg=%d",
+ "PR: Unexpected Authentication frame, auth_alg=%d, ignore the frame",
auth_alg);
- return -1;
+ return 0;
}
auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
diff --git a/src/common/proximity_ranging.h b/src/common/proximity_ranging.h
index 6ce9f6922..785659501 100644
--- a/src/common/proximity_ranging.h
+++ b/src/common/proximity_ranging.h
@@ -659,6 +659,7 @@ struct operation_mode {
struct pr_data * pr_init(const struct pr_config *cfg);
void pr_flush(struct pr_data *pr);
+void pr_reset_pasn_state(struct pr_data *pr, const u8 *addr);
void pr_deinit(struct pr_data *pr);
void pr_set_dev_addr(struct pr_data *pr, const u8 *addr);
void pr_clear_dev_iks(struct pr_data *pr);
diff --git a/wpa_supplicant/pr_supplicant.c b/wpa_supplicant/pr_supplicant.c
index 7d929613e..a275f2937 100644
--- a/wpa_supplicant/pr_supplicant.c
+++ b/wpa_supplicant/pr_supplicant.c
@@ -392,6 +392,10 @@ static void wpas_pr_pasn_result(void *ctx, u8 role, u8 protocol_type,
static void wpas_pr_clear_ranging_params(struct pr_data *pr)
{
if (pr) {
+ if (pr->pr_pasn_params)
+ pr_reset_pasn_state(pr, pr->pr_pasn_params->peer_addr);
+ else
+ pr_reset_pasn_state(pr, NULL);
os_free(pr->pr_pasn_params);
pr->pr_pasn_params = NULL;
pr->ranging_final_received = false;
@@ -1341,6 +1345,9 @@ int wpas_pr_initiate_pasn_auth(struct wpa_supplicant *wpa_s,
if (pr && pr_ensure_oob_peer(pr, peer_addr, freq) < 0)
return -1;
+ if (pr)
+ pr_reset_pasn_state(pr, peer_addr);
+
if (pasn_role == PR_ROLE_PASN_RESPONDER) {
struct wpa_pr_pasn_roc_work *rwork;
unsigned int roc_time_ms = PR_PASN_RESPONDER_ROC_DURATION;
--
2.56.0.385.gac671c819a-goog
More information about the Hostap
mailing list