From: Andrea Covelli During an AP-side Fast Transition, hostapd may install the PTK after creating a station entry but before marking it associated. The ASSOC gate in ieee80211_add_key() rejects the request with -ENOENT, producing: nl80211: kernel reports: key addition failed Userspace may retry after association, but this race can instead break the roam, particularly with PMF. Accept pre-association pairwise keys on AP and AP_VLAN interfaces once the station exists. Mark only those keys as deferred so hardware upload is skipped while the key is stored in mac80211. Upload the marked PTKs after the driver's AUTH-to-ASSOC state transition succeeds. Track deferred state on each key and scan the station's PTK slots at ASSOC so hardware upload is limited to keys accepted before association. EPP peers are excluded from this deferral because EPP requires the PTK to be available before association to encrypt and decrypt (Re)Association Request and Response frames. Fixes: 1626e0fa740d ("mac80211: fix FT roaming") Cc: stable@vger.kernel.org Link: https://github.com/openwrt/openwrt/pull/23181 Assisted-by: Codex:gpt-5.6-sol Signed-off-by: Andrea Covelli --- A backport of this change was tested with mt76 on Cudy WR3000E v1 and WR3000P v1 devices running OpenWrt 25.12.5. Repeated bidirectional 802.11r FT roams completed without new "key addition failed" messages. net/mac80211/cfg.c | 18 +++++++++++++++--- net/mac80211/key.c | 27 +++++++++++++++++++++++++++ net/mac80211/key.h | 4 ++++ net/mac80211/sta_info.c | 1 + 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 0a9247be26af..a95435b720a9 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -666,7 +666,14 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev, key->conf.flags |= IEEE80211_KEY_FLAG_NO_AUTO_TX; if (mac_addr) { + bool defer_hw_upload; + sta = sta_info_get_bss(sdata, mac_addr); + defer_hw_upload = + sta && pairwise && !sta->sta.epp_peer && + !test_sta_flag(sta, WLAN_STA_ASSOC) && + (sdata->vif.type == NL80211_IFTYPE_AP || + sdata->vif.type == NL80211_IFTYPE_AP_VLAN); /* * The ASSOC test makes sure the driver is ready to * receive the key. When wpa_supplicant has roamed @@ -681,14 +688,19 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev, * If (re)association frame encryption support is not present, * cfg80211 will not allow key installation in non‑AP STA mode. * - * TODO: accept the key if we have a station entry and - * add it to the device after the station associates. + * AP-side FT may also install a pairwise key before the + * station is associated. Keep it in mac80211 and upload it + * to the driver after the station reaches ASSOC. */ if (!sta || (!sta->sta.epp_peer && - !test_sta_flag(sta, WLAN_STA_ASSOC))) { + !test_sta_flag(sta, WLAN_STA_ASSOC) && + !defer_hw_upload)) { ieee80211_key_free_unused(key); return -ENOENT; } + + if (defer_hw_upload) + key->flags |= KEY_FLAG_DEFERRED_HW_UPLOAD; } switch (sdata->vif.type) { diff --git a/net/mac80211/key.c b/net/mac80211/key.c index f45e792abede..180e2aa9649e 100644 --- a/net/mac80211/key.c +++ b/net/mac80211/key.c @@ -144,6 +144,11 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key) return -EINVAL; } + if (key->flags & KEY_FLAG_DEFERRED_HW_UPLOAD) { + ret = 1; + goto out_unsupported; + } + if (!key->local->ops->set_key) goto out_unsupported; @@ -997,6 +1002,28 @@ void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata) } } +void ieee80211_upload_deferred_sta_keys(struct sta_info *sta) +{ + struct ieee80211_local *local = sta->local; + struct ieee80211_key *key; + int i, ret; + + lockdep_assert_wiphy(local->hw.wiphy); + + for (i = 0; i < ARRAY_SIZE(sta->ptk); i++) { + key = wiphy_dereference(local->hw.wiphy, sta->ptk[i]); + if (!key || !(key->flags & KEY_FLAG_DEFERRED_HW_UPLOAD)) + continue; + + key->flags &= ~KEY_FLAG_DEFERRED_HW_UPLOAD; + ret = ieee80211_key_enable_hw_accel(key); + if (ret) + sdata_err(key->sdata, + "failed to enable deferred key (%d, %pM): %d\n", + key->conf.keyidx, sta->sta.addr, ret); + } +} + static void ieee80211_key_iter(struct ieee80211_hw *hw, struct ieee80211_vif *vif, diff --git a/net/mac80211/key.h b/net/mac80211/key.h index 826e4e9387c5..97d3bbcf0ac2 100644 --- a/net/mac80211/key.h +++ b/net/mac80211/key.h @@ -32,10 +32,13 @@ struct sta_info; * @KEY_FLAG_UPLOADED_TO_HARDWARE: Indicates that this key is present * in the hardware for TX crypto hardware acceleration. * @KEY_FLAG_TAINTED: Key is tainted and packets should be dropped. + * @KEY_FLAG_DEFERRED_HW_UPLOAD: Key upload is deferred until the station + * is associated. */ enum ieee80211_internal_key_flags { KEY_FLAG_UPLOADED_TO_HARDWARE = BIT(0), KEY_FLAG_TAINTED = BIT(1), + KEY_FLAG_DEFERRED_HW_UPLOAD = BIT(2), }; enum ieee80211_internal_tkip_state { @@ -165,6 +168,7 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata, bool force_synchronize); void ieee80211_free_sta_keys(struct ieee80211_local *local, struct sta_info *sta); +void ieee80211_upload_deferred_sta_keys(struct sta_info *sta); void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata); int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata, unsigned long del_links_mask, diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index d12aed9c1756..625f628b46a0 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -1468,6 +1468,7 @@ static int _sta_info_move_state(struct sta_info *sta, case IEEE80211_STA_ASSOC: if (sta->sta_state == IEEE80211_STA_AUTH) { set_bit(WLAN_STA_ASSOC, &sta->_flags); + ieee80211_upload_deferred_sta_keys(sta); sta->assoc_at = ktime_get_boottime_ns(); if (recalc) { ieee80211_recalc_min_chandef(sta->sdata, -1); -- 2.53.0