From 4eaea07ff374c7c663f707a229d27dd2fbe38ecb Mon Sep 17 00:00:00 2001 From: Gus Date: Thu, 26 Mar 2026 18:43:08 +0000 Subject: [PATCH] wifi: mt76: mt7996: fix FT-SAE by adding BIP batch key handling mt7996's key installation path (mt7996_mcu_sta_key_tlv) installs BIP keys individually, while the mt76_connac path used by mt7915 installs them as a batch of two keys (AES_CCMP + BIP_CMAC_128). This causes FT-SAE (802.11r with WPA3) to fail when the mt7996 is the target AP, as the management frame protection keys are not properly paired with the pairwise key during fast BSS transition. The symptom is that hostapd completes the FT authentication successfully but the driver fails to deliver the association response to the client ("did not acknowledge association response"), causing the client to fall back to full SAE re-authentication. This was confirmed through extensive testing: FT-PSK (WPA2) works on mt7996, FT-SAE works on mt7915 (which uses the connac BIP batch path), but FT-SAE fails on mt7996. The fix adds BIP_CMAC_128 batch mode handling to mt7996_mcu_sta_key_tlv, matching the logic in mt76_connac_mcu_sta_key_tlv: - When BIP_CMAC_128 cipher is received, install two keys: the previously-cached AES_CCMP key plus the BIP key - When AES_CCMP cipher is received, cache it in the per-station sta_key_conf for the subsequent BIP batch update - Pass the existing but unused msta_link->bip field through the key installation call chain Tested on BananaPi BPI-R4 (MT7988 + BE14 WiFi module) with ft_over_ds=1 and pmk_r1_push=1, achieving 100% FT-SAE success rate across 2.4GHz, 5GHz, and 6GHz bands. Fixes: https://github.com/openwrt/openwrt/issues/9181 Signed-off-by: Gus Bourg --- mt7996/main.c | 2 +- mt7996/mcu.c | 38 +++++++++++++++++++++++++++++++++++++- mt7996/mt7996.h | 1 + 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/mt7996/main.c b/mt7996/main.c index 6d922d7a3..2d4a40b30 100644 --- a/mt7996/main.c +++ b/mt7996/main.c @@ -258,7 +258,7 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); - err = mt7996_mcu_add_key(&dev->mt76, link, key, + err = mt7996_mcu_add_key(&dev->mt76, link, &msta_link->bip, key, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), &msta_link->wcid, cmd); diff --git a/mt7996/mcu.c b/mt7996/mcu.c index 3dcbfed0b..7fb9f64f4 100644 --- a/mt7996/mcu.c +++ b/mt7996/mcu.c @@ -2862,6 +2862,7 @@ void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta) static int mt7996_mcu_sta_key_tlv(struct mt76_dev *dev, struct mt76_wcid *wcid, struct sk_buff *skb, + struct mt76_connac_sta_key_conf *sta_key_conf, struct ieee80211_key_conf *key, enum set_key_cmd cmd) { @@ -2888,6 +2889,34 @@ mt7996_mcu_sta_key_tlv(struct mt76_dev *dev, struct mt76_wcid *wcid, if (cipher == MCU_CIPHER_NONE) return -EOPNOTSUPP; + if (cipher == MCU_CIPHER_BIP_CMAC_128) { + /* BIP batch mode: install CCMP + BIP as two keys, matching + * the approach used by mt76_connac_mcu_sta_key_tlv() for + * mt7915. This is required for FT-SAE (802.11r with WPA3) + * to work correctly, as the management frame protection + * keys must be installed alongside the pairwise key. + */ + sec_key->mgmt_prot = 0; + sec_key->cipher_id = MCU_CIPHER_AES_CCMP; + sec_key->cipher_len = sizeof(*sec_key); + sec_key->key_id = sta_key_conf->keyidx; + sec_key->key_len = 16; + sec_key->need_resp = 0; + memcpy(sec_key->key, sta_key_conf->key, 16); + + sec_key = &sec->key[1]; + sec_key->wlan_idx = cpu_to_le16(wcid->idx); + sec_key->mgmt_prot = 1; + sec_key->cipher_id = MCU_CIPHER_BIP_CMAC_128; + sec_key->cipher_len = sizeof(*sec_key); + sec_key->key_id = key->keyidx; + sec_key->key_len = 16; + sec_key->need_resp = 0; + memcpy(sec_key->key, key->key, 16); + sec->n_cipher = 2; + return 0; + } + sec_key->mgmt_prot = 0; sec_key->cipher_id = cipher; sec_key->cipher_len = sizeof(*sec_key); @@ -2902,6 +2931,12 @@ mt7996_mcu_sta_key_tlv(struct mt76_dev *dev, struct mt76_wcid *wcid, return 0; } + /* store key_conf for BIP batch update */ + if (cipher == MCU_CIPHER_AES_CCMP) { + memcpy(sta_key_conf->key, key->key, key->keylen); + sta_key_conf->keyidx = key->keyidx; + } + if (sec_key->key_id != 6 && sec_key->key_id != 7) return 0; @@ -2930,6 +2965,7 @@ mt7996_mcu_sta_key_tlv(struct mt76_dev *dev, struct mt76_wcid *wcid, } int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_vif_link *link, + struct mt76_connac_sta_key_conf *sta_key_conf, struct ieee80211_key_conf *key, int mcu_cmd, struct mt76_wcid *wcid, enum set_key_cmd cmd) { @@ -2941,7 +2977,7 @@ int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_vif_link *link, if (IS_ERR(skb)) return PTR_ERR(skb); - ret = mt7996_mcu_sta_key_tlv(dev, wcid, skb, key, cmd); + ret = mt7996_mcu_sta_key_tlv(dev, wcid, skb, sta_key_conf, key, cmd); if (ret) { dev_kfree_skb(skb); return ret; diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h index 0dc4198fc..4f73ddf5d 100644 --- a/mt7996/mt7996.h +++ b/mt7996/mt7996.h @@ -914,6 +914,7 @@ int mt7996_init_debugfs(struct mt7996_dev *dev); void mt7996_debugfs_rx_fw_monitor(struct mt7996_dev *dev, const void *data, int len); bool mt7996_debugfs_rx_log(struct mt7996_dev *dev, const void *data, int len); int mt7996_mcu_add_key(struct mt76_dev *dev, struct mt7996_vif_link *link, + struct mt76_connac_sta_key_conf *sta_key_conf, struct ieee80211_key_conf *key, int mcu_cmd, struct mt76_wcid *wcid, enum set_key_cmd cmd); int mt7996_mcu_bcn_prot_enable(struct mt7996_dev *dev,