hostapd: Avoid key reinstallation in FT handshake

Do not reinstall TK to the driver during Reassociation Response frame
processing if the first attempt of setting the TK succeeded. This avoids
issues related to clearing the TX/RX PN that could result in reusing
same PN values for transmitted frames (e.g., due to CCM nonce reuse and
also hitting replay protection on the receiver) and accepting replayed
frames on RX side.

This issue was introduced by the commit
0e84c25434e6a1f283c7b4e62e483729085b78d2 ('FT: Fix PTK configuration in
authenticator') which allowed wpa_ft_install_ptk() to be called multiple
times with the same PTK. While the second configuration attempt is
needed with some drivers, it must be done only if the first attempt
failed.

Bug: 65245581
Test: WiFi integration tests
Change-Id: I9d60b25a655eeb2dc19694e49d2f34d537849a13
Merged-In: Ieb2913762eef0ea49a7c6b3b0782e709653f96bb
Signed-off-by: Mathy Vanhoef <Mathy.Vanhoef@cs.kuleuven.be>
Signed-off-by: Glen Kuhne <kuh@google.com>
(cherry picked from commit e3db9d2d56c7c07c3632bab770bbcb3f881c1ee2)
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index e1a6712..c6234dc 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -2209,6 +2209,7 @@
 {
 	struct ieee80211_ht_capabilities ht_cap;
 	struct ieee80211_vht_capabilities vht_cap;
+	int set = 1;
 
 	/*
 	 * Remove the STA entry to ensure the STA PS state gets cleared and
@@ -2216,9 +2217,18 @@
 	 * FT-over-the-DS, where a station re-associates back to the same AP but
 	 * skips the authentication flow, or if working with a driver that
 	 * does not support full AP client state.
+	 *
+	 * Skip this if the STA has already completed FT reassociation and the
+	 * TK has been configured since the TX/RX PN must not be reset to 0 for
+	 * the same key.
 	 */
-	if (!sta->added_unassoc)
+	if (!sta->added_unassoc &&
+	    (!(sta->flags & WLAN_STA_AUTHORIZED) ||
+	     !wpa_auth_sta_ft_tk_already_set(sta->wpa_sm))) {
 		hostapd_drv_sta_remove(hapd, sta->addr);
+		wpa_auth_sm_event(sta->wpa_sm, WPA_DRV_STA_REMOVED);
+		set = 0;
+	}
 
 #ifdef CONFIG_IEEE80211N
 	if (sta->flags & WLAN_STA_HT)
@@ -2241,11 +2251,11 @@
 			    sta->flags & WLAN_STA_VHT ? &vht_cap : NULL,
 			    sta->flags | WLAN_STA_ASSOC, sta->qosinfo,
 			    sta->vht_opmode, sta->p2p_ie ? 1 : 0,
-			    sta->added_unassoc)) {
+			    set)) {
 		hostapd_logger(hapd, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE,
 			       "Could not %s STA to kernel driver",
-			       sta->added_unassoc ? "set" : "add");
+			       set ? "set" : "add");
 
 		if (sta->added_unassoc) {
 			hostapd_drv_sta_remove(hapd, sta->addr);
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 7b26c04..71f468b 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -1751,6 +1751,9 @@
 #else /* CONFIG_IEEE80211R_AP */
 		break;
 #endif /* CONFIG_IEEE80211R_AP */
+	case WPA_DRV_STA_REMOVED:
+		sm->tk_already_set = FALSE;
+		return 0;
 	}
 
 #ifdef CONFIG_IEEE80211R_AP
@@ -3734,6 +3737,14 @@
 }
 
 
+int wpa_auth_sta_ft_tk_already_set(struct wpa_state_machine *sm)
+{
+	if (!sm || !wpa_key_mgmt_ft(sm->wpa_key_mgmt))
+		return 0;
+	return sm->tk_already_set;
+}
+
+
 int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm,
 			     struct rsn_pmksa_cache_entry *entry)
 {
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 0920a16..387d146 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -270,7 +270,7 @@
 		 u8 *data, size_t data_len);
 enum wpa_event {
 	WPA_AUTH, WPA_ASSOC, WPA_DISASSOC, WPA_DEAUTH, WPA_REAUTH,
-	WPA_REAUTH_EAPOL, WPA_ASSOC_FT
+	WPA_REAUTH_EAPOL, WPA_ASSOC_FT, WPA_DRV_STA_REMOVED
 };
 void wpa_remove_ptk(struct wpa_state_machine *sm);
 int wpa_auth_sm_event(struct wpa_state_machine *sm, enum wpa_event event);
@@ -283,6 +283,7 @@
 int wpa_auth_get_pairwise(struct wpa_state_machine *sm);
 int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm);
 int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm);
+int wpa_auth_sta_ft_tk_already_set(struct wpa_state_machine *sm);
 int wpa_auth_sta_clear_pmksa(struct wpa_state_machine *sm,
 			     struct rsn_pmksa_cache_entry *entry);
 struct rsn_pmksa_cache_entry *
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index c267a17..210d300 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -797,6 +797,14 @@
 		return;
 	}
 
+	if (sm->tk_already_set) {
+		/* Must avoid TK reconfiguration to prevent clearing of TX/RX
+		 * PN in the driver */
+		wpa_printf(MSG_DEBUG,
+			   "FT: Do not re-install same PTK to the driver");
+		return;
+	}
+
 	/* FIX: add STA entry to kernel/driver here? The set_key will fail
 	 * most likely without this.. At the moment, STA entry is added only
 	 * after association has been completed. This function will be called
@@ -809,6 +817,7 @@
 
 	/* FIX: MLME-SetProtection.Request(TA, Tx_Rx) */
 	sm->pairwise_set = TRUE;
+	sm->tk_already_set = TRUE;
 }
 
 
@@ -1006,6 +1015,7 @@
 
 	sm->pairwise = pairwise;
 	sm->PTK_valid = TRUE;
+	sm->tk_already_set = FALSE;
 	wpa_ft_install_ptk(sm);
 
 	buflen = 2 + sizeof(struct rsn_mdie) + 2 + sizeof(struct rsn_ftie) +
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 90318d8..9564f24 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -58,6 +58,7 @@
 	struct wpa_ptk PTK;
 	Boolean PTK_valid;
 	Boolean pairwise_set;
+	Boolean tk_already_set;
 	int keycount;
 	Boolean Pair;
 	struct wpa_key_replay_counter {