For a frame that the hardware decrypted, mt76_check_ccmp_pn() compares the PN with the last PN of status->wcid. For an MLD peer, mt7996 reports each frame on the wcid of the link it arrived on, so every link keeps its own replay counter, although all links of the MLD share the PN space of the key. With the IV stripped, the function also sets RX_FLAG_PN_VALIDATED, so mac80211 skips its own check per key. A frame received on one link and replayed on another one thus passes the replay check whenever the second link has seen lower PNs. Keep the replay counters of a peer on its default wcid (def_wcid), like the reorder state in mt76_rx_aggr_reorder(). The key check still uses the wcid of the link. This also applies to mt7925, which sets def_wcid for the links of an MLD station. A key install resets these counters to the RX sequence of the key in mac80211, which frames marked RX_FLAG_PN_VALIDATED never advance. In mt7996 and mt7925, the key of a station is set up link by link, and in mt7996 a link that is added later gets the installed keys again. Each of these setups would reset the counters of the whole peer, so that a frame of the new key received on one link could be replayed after the setup of the next one. Seed the counters on the default wcid once per key install, before the key reaches any link of the station, and only enable the check on each link. Fixes: dd82a9e02c05 ("wifi: mt76: mt7996: Rely on mt7996_sta_link in sta_add/sta_remove callbacks") Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 18 ++++++++---------- drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++ .../net/wireless/mediatek/mt76/mt7925/main.c | 13 +++++++++++-- .../net/wireless/mediatek/mt76/mt7996/main.c | 11 ++++++++++- 4 files changed, 36 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 76908bd835f2..08ed3fc6d386 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1209,16 +1209,10 @@ void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, struct ieee80211_key_seq seq; int i; - wcid->rx_check_pn = false; - - if (!key) + mt76_wcid_key_link_setup(wcid, key); + if (!wcid->rx_check_pn) return; - if (key->cipher != WLAN_CIPHER_SUITE_CCMP) - return; - - wcid->rx_check_pn = true; - /* data frame */ for (i = 0; i < IEEE80211_NUM_TIDS; i++) { ieee80211_get_key_rx_seq(key, i, &seq); @@ -1327,6 +1321,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb) struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; struct mt76_wcid *wcid = status->wcid; struct ieee80211_hdr *hdr; + struct mt76_wcid *pn_wcid; int security_idx; int ret; @@ -1339,6 +1334,9 @@ mt76_check_ccmp_pn(struct sk_buff *skb) if (!wcid || !wcid->rx_check_pn) return; + /* the links of an MLD peer share one PN space per key */ + pn_wcid = mt76_wcid_primary(wcid); + security_idx = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK; if (status->flag & RX_FLAG_8023) goto skip_hdr_check; @@ -1366,14 +1364,14 @@ mt76_check_ccmp_pn(struct sk_buff *skb) skip_hdr_check: BUILD_BUG_ON(sizeof(status->iv) != sizeof(wcid->rx_key_pn[0])); - ret = memcmp(status->iv, wcid->rx_key_pn[security_idx], + ret = memcmp(status->iv, pn_wcid->rx_key_pn[security_idx], sizeof(status->iv)); if (ret <= 0) { status->flag |= RX_FLAG_ONLY_MONITOR; return; } - memcpy(wcid->rx_key_pn[security_idx], status->iv, sizeof(status->iv)); + memcpy(pn_wcid->rx_key_pn[security_idx], status->iv, sizeof(status->iv)); if (status->flag & RX_FLAG_IV_STRIPPED) status->flag |= RX_FLAG_PN_VALIDATED; diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index fce7bf2f0333..477d33a0022d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1574,6 +1574,13 @@ int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid, u16 ssn, u16 size); void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tid); +static inline void +mt76_wcid_key_link_setup(struct mt76_wcid *wcid, + struct ieee80211_key_conf *key) +{ + wcid->rx_check_pn = key && key->cipher == WLAN_CIPHER_SUITE_CCMP; +} + void mt76_wcid_key_setup(struct mt76_dev *dev, struct mt76_wcid *wcid, struct ieee80211_key_conf *key); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 22cc5b49da0a..ffbc6b954632 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -707,8 +707,11 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, else goto out; - mt76_wcid_key_setup(&dev->mt76, wcid, - cmd == SET_KEY ? key : NULL); + if (sta) + mt76_wcid_key_link_setup(wcid, cmd == SET_KEY ? key : NULL); + else + mt76_wcid_key_setup(&dev->mt76, wcid, + cmd == SET_KEY ? key : NULL); err = mt7925_mcu_add_key(&dev->mt76, vif, &mlink->bip, key, MCU_UNI_CMD(STA_REC_UPDATE), @@ -749,6 +752,12 @@ static int mt7925_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, mt792x_mutex_acquire(dev); + /* the links of a station share one RX PN state, seeded before the + * key reaches any of them + */ + if (sta && cmd == SET_KEY) + mt76_wcid_key_setup(&dev->mt76, &msta->deflink.wcid, key); + if (ieee80211_vif_is_mld(vif)) { unsigned int link_id; unsigned long add; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index ec7c5e8f3fda..82143ade1cc0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -268,7 +268,7 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, if (cmd != SET_KEY && !is_bigtk) return 0; - mt76_wcid_key_setup(&dev->mt76, &msta_link->wcid, key); + mt76_wcid_key_link_setup(&msta_link->wcid, key); err = mt7996_mcu_add_key(&dev->mt76, link, key, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), @@ -837,6 +837,15 @@ static int mt7996_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, else links = BIT(0); + /* the links of a station share one RX PN state, seeded before the + * key reaches any of them + */ + if (sta && cmd == SET_KEY) { + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + + mt76_wcid_key_setup(&dev->mt76, &msta->deflink.wcid, key); + } + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { err = mt7996_set_hw_key(hw, cmd, vif, sta, link_id, key); if (err) -- 2.47.3