[PATCH 1/3] PR: Ignore duplicate and out-of-order PASN authentication frames

Emily Xia xiayucheng at google.com
Mon Oct 26 22:57:01 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>
---
 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.307.g5fe7bd680c-goog




More information about the Hostap mailing list