* wifi: cfg80211/mac80211: key handling improvements
@ 2026-10-13 12:59 Johannes Berg
2026-10-13 12:59 ` [PATCH 01/11] wifi: cfg80211: reject WEP/TKIP with multi-link Johannes Berg
` (10 more replies)
0 siblings, 11 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless
While working on SMD Transition, and in order to actually do that,
I found that I finally need to link all GTKs to stations, not just
in IBSS and mesh modes but also in client mode.
This is effectively the outcome of that work, some things around
that as well; the core changes are patches 3 and 5, with 6 enabling
drivers to also get it in the API that way.
johannes
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 01/11] wifi: cfg80211: reject WEP/TKIP with multi-link
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 02/11] wifi: mac80211: replace ieee80211_iter_keys_rcu() Johannes Berg
` (9 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
This is not possible by spec, so check and reject it in
order to avoid being able to hit such code paths in the
code later.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/wireless/util.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 9d5456defc90..59912f4a93c8 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -426,6 +426,17 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
break;
}
+ switch (params->cipher) {
+ case WLAN_CIPHER_SUITE_WEP40:
+ case WLAN_CIPHER_SUITE_WEP104:
+ case WLAN_CIPHER_SUITE_TKIP:
+ if (wdev->valid_links)
+ return -EINVAL;
+ break;
+ default:
+ break;
+ }
+
/*
* Per Wi-Fi Aware v4.0 section 7.1.2, NAN Data interfaces
* shall only use CCMP-128 or GCMP-256.
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 02/11] wifi: mac80211: replace ieee80211_iter_keys_rcu()
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
2026-10-13 12:59 ` [PATCH 01/11] wifi: cfg80211: reject WEP/TKIP with multi-link Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 03/11] wifi: cfg80211: support C/B/I/GTK per AP in client modes Johannes Berg
` (8 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
This function is unsafe since we reuse the key->list for
deletions after RCU removal. Use a spinlock instead to
protect that, since otherwise we'd have to add a new list
head for a very uncommon case.
Fixes: ef044763a3ca ("mac80211: add atomic uploaded keys iterator")
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
drivers/net/wireless/intel/iwlwifi/mld/d3.c | 6 +-
drivers/net/wireless/intel/iwlwifi/mvm/ops.c | 4 +-
.../net/wireless/mediatek/mt76/mt76x02_mmio.c | 4 +-
drivers/net/wireless/realtek/rtw88/main.c | 4 +-
drivers/net/wireless/realtek/rtw88/util.h | 4 +-
include/net/mac80211.h | 22 +++----
net/mac80211/ieee80211_i.h | 3 +
net/mac80211/key.c | 62 +++++++++++--------
net/mac80211/key.h | 2 +-
net/mac80211/main.c | 1 +
10 files changed, 60 insertions(+), 52 deletions(-)
diff --git a/drivers/net/wireless/intel/iwlwifi/mld/d3.c b/drivers/net/wireless/intel/iwlwifi/mld/d3.c
index 6d8ede336d59..74165bf5c42a 100644
--- a/drivers/net/wireless/intel/iwlwifi/mld/d3.c
+++ b/drivers/net/wireless/intel/iwlwifi/mld/d3.c
@@ -1120,9 +1120,9 @@ iwl_mld_process_rsc_notification(struct iwl_mld *mld,
};
/* Iterate through all active keys and update RSC */
- ieee80211_iter_keys_rcu(mld->hw, vif,
- iwl_mld_rsc_update_key_iter,
- &iter_data);
+ ieee80211_iter_keys_atomic(mld->hw, vif,
+ iwl_mld_rsc_update_key_iter,
+ &iter_data);
}
static void
diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c
index 1dd27292af9b..fb64b95a632e 100644
--- a/drivers/net/wireless/intel/iwlwifi/mvm/ops.c
+++ b/drivers/net/wireless/intel/iwlwifi/mvm/ops.c
@@ -987,9 +987,7 @@ static void iwl_mvm_frob_txf(void *ctx, void *buf, size_t buflen)
if (iwl_mvm_has_new_tx_api(mvm))
return;
- rcu_read_lock();
- ieee80211_iter_keys_rcu(mvm->hw, NULL, iwl_mvm_frob_txf_key_iter, &txf);
- rcu_read_unlock();
+ ieee80211_iter_keys_atomic(mvm->hw, NULL, iwl_mvm_frob_txf_key_iter, &txf);
}
static void iwl_mvm_frob_hcmd(void *ctx, void *hcmd, size_t len)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
index dc7c03d23123..2caba7f5f97f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c
@@ -398,9 +398,7 @@ static void mt76x02_reset_state(struct mt76x02_dev *dev)
clear_bit(MT76_STATE_RUNNING, &dev->mphy.state);
- rcu_read_lock();
- ieee80211_iter_keys_rcu(dev->mt76.hw, NULL, mt76x02_key_sync, NULL);
- rcu_read_unlock();
+ ieee80211_iter_keys_atomic(dev->mt76.hw, NULL, mt76x02_key_sync, NULL);
for (i = 0; i < MT76x02_N_WCIDS; i++) {
struct ieee80211_sta *sta;
diff --git a/drivers/net/wireless/realtek/rtw88/main.c b/drivers/net/wireless/realtek/rtw88/main.c
index 0f23498b5c96..f7708ad17b6f 100644
--- a/drivers/net/wireless/realtek/rtw88/main.c
+++ b/drivers/net/wireless/realtek/rtw88/main.c
@@ -662,9 +662,7 @@ static void __fw_recovery_work(struct rtw_dev *rtwdev)
WARN(1, "firmware crash, start reset and recover\n");
- rcu_read_lock();
- rtw_iterate_keys_rcu(rtwdev, NULL, rtw_reset_key_iter, rtwdev);
- rcu_read_unlock();
+ rtw_iterate_keys_atomic(rtwdev, NULL, rtw_reset_key_iter, rtwdev);
rtw_iterate_stas_atomic(rtwdev, rtw_reset_sta_iter, rtwdev);
rtw_iterate_vifs_atomic(rtwdev, rtw_reset_vif_iter, rtwdev);
bitmap_zero(rtwdev->hw_port, RTW_PORT_NUM);
diff --git a/drivers/net/wireless/realtek/rtw88/util.h b/drivers/net/wireless/realtek/rtw88/util.h
index f8399128a9a3..dff02057bd34 100644
--- a/drivers/net/wireless/realtek/rtw88/util.h
+++ b/drivers/net/wireless/realtek/rtw88/util.h
@@ -14,8 +14,8 @@ struct rtw_dev;
ieee80211_iterate_stations_atomic(rtwdev->hw, iterator, data)
#define rtw_iterate_keys(rtwdev, vif, iterator, data) \
ieee80211_iter_keys(rtwdev->hw, vif, iterator, data)
-#define rtw_iterate_keys_rcu(rtwdev, vif, iterator, data) \
- ieee80211_iter_keys_rcu((rtwdev)->hw, vif, iterator, data)
+#define rtw_iterate_keys_atomic(rtwdev, vif, iterator, data) \
+ ieee80211_iter_keys_atomic((rtwdev)->hw, vif, iterator, data)
void rtw_iterate_vifs(struct rtw_dev *rtwdev,
void (*iterator)(void *data, struct ieee80211_vif *vif),
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index a1a6409b44c1..2272d7c85dd4 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -6962,7 +6962,7 @@ void ieee80211_iter_keys(struct ieee80211_hw *hw,
void *iter_data);
/**
- * ieee80211_iter_keys_rcu - iterate keys programmed into the device
+ * ieee80211_iter_keys_atomic - iterate keys programmed into the device
* @hw: pointer obtained from ieee80211_alloc_hw()
* @vif: virtual interface to iterate, may be %NULL for all
* @iter: iterator function that will be called for each key
@@ -6973,17 +6973,17 @@ void ieee80211_iter_keys(struct ieee80211_hw *hw,
* the device. Note that due to locking reasons, keys of station
* in removal process will be skipped.
*
- * This function requires being called in an RCU critical section,
- * and thus iter must be atomic.
+ * This function is atomic, so the iter must be as well. Cannot
+ * be used from hardirq context.
*/
-void ieee80211_iter_keys_rcu(struct ieee80211_hw *hw,
- struct ieee80211_vif *vif,
- void (*iter)(struct ieee80211_hw *hw,
- struct ieee80211_vif *vif,
- struct ieee80211_sta *sta,
- struct ieee80211_key_conf *key,
- void *data),
- void *iter_data);
+void ieee80211_iter_keys_atomic(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ void (*iter)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key,
+ void *data),
+ void *iter_data);
/**
* ieee80211_iter_chan_contexts_atomic - iterate channel contexts
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 1a6cbbb5a17d..52e413a20f71 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1533,6 +1533,9 @@ struct ieee80211_local {
/* protects the aggregated multicast list and filter calls */
spinlock_t filter_lock;
+ /* protects atomic iteration of each sdata key_list */
+ spinlock_t key_lock;
+
/* used for uploading changed mc list */
struct wiphy_work reconfig_filter;
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index 4c9788ec0b74..b55de973a929 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -532,8 +532,11 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
if (ret)
return ret;
- if (new)
- list_add_tail_rcu(&new->list, &sdata->key_list);
+ if (new) {
+ spin_lock_bh(&sdata->local->key_lock);
+ list_add_tail(&new->list, &sdata->key_list);
+ spin_unlock_bh(&sdata->local->key_lock);
+ }
if (sta) {
if (pairwise) {
@@ -596,8 +599,11 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
new->conf.keyidx);
}
- if (old)
- list_del_rcu(&old->list);
+ if (old) {
+ spin_lock_bh(&sdata->local->key_lock);
+ list_del(&old->list);
+ spin_unlock_bh(&sdata->local->key_lock);
+ }
return 0;
}
@@ -803,8 +809,8 @@ static void ieee80211_key_destroy(struct ieee80211_key *key,
return;
/*
- * Synchronize so the TX path and rcu key iterators
- * can no longer be using this key before we free/remove it.
+ * Synchronize so the TX path can no longer be using this
+ * key before we free/remove it.
*/
synchronize_net();
@@ -1078,42 +1084,46 @@ void ieee80211_iter_keys(struct ieee80211_hw *hw,
EXPORT_SYMBOL(ieee80211_iter_keys);
static void
-_ieee80211_iter_keys_rcu(struct ieee80211_hw *hw,
- struct ieee80211_sub_if_data *sdata,
- void (*iter)(struct ieee80211_hw *hw,
- struct ieee80211_vif *vif,
- struct ieee80211_sta *sta,
- struct ieee80211_key_conf *key,
- void *data),
- void *iter_data)
+_ieee80211_iter_keys_atomic(struct ieee80211_hw *hw,
+ struct ieee80211_sub_if_data *sdata,
+ void (*iter)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key,
+ void *data),
+ void *iter_data)
{
struct ieee80211_key *key;
- list_for_each_entry_rcu(key, &sdata->key_list, list)
+ list_for_each_entry(key, &sdata->key_list, list)
ieee80211_key_iter(hw, &sdata->vif, key, iter, iter_data);
}
-void ieee80211_iter_keys_rcu(struct ieee80211_hw *hw,
- struct ieee80211_vif *vif,
- void (*iter)(struct ieee80211_hw *hw,
- struct ieee80211_vif *vif,
- struct ieee80211_sta *sta,
- struct ieee80211_key_conf *key,
- void *data),
- void *iter_data)
+void ieee80211_iter_keys_atomic(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ void (*iter)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_sta *sta,
+ struct ieee80211_key_conf *key,
+ void *data),
+ void *iter_data)
{
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *sdata;
+ spin_lock_bh(&local->key_lock);
if (vif) {
sdata = vif_to_sdata(vif);
- _ieee80211_iter_keys_rcu(hw, sdata, iter, iter_data);
+ _ieee80211_iter_keys_atomic(hw, sdata, iter, iter_data);
} else {
+ rcu_read_lock();
list_for_each_entry_rcu(sdata, &local->interfaces, list)
- _ieee80211_iter_keys_rcu(hw, sdata, iter, iter_data);
+ _ieee80211_iter_keys_atomic(hw, sdata, iter, iter_data);
+ rcu_read_unlock();
}
+ spin_unlock_bh(&local->key_lock);
}
-EXPORT_SYMBOL(ieee80211_iter_keys_rcu);
+EXPORT_SYMBOL(ieee80211_iter_keys_atomic);
static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata,
struct list_head *keys)
diff --git a/net/mac80211/key.h b/net/mac80211/key.h
index c85992493afd..c8531b516c1c 100644
--- a/net/mac80211/key.h
+++ b/net/mac80211/key.h
@@ -62,7 +62,7 @@ struct ieee80211_key {
struct ieee80211_sub_if_data *sdata;
struct sta_info *sta;
- /* for sdata list */
+ /* for sdata list - note reuse and local->key_lock locking */
struct list_head list;
/* protected by key mutex */
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 76e85754deff..2d60fe5b3f91 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -990,6 +990,7 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
spin_lock_init(&local->filter_lock);
spin_lock_init(&local->rx_path_lock);
spin_lock_init(&local->queue_stop_reason_lock);
+ spin_lock_init(&local->key_lock);
local->aql_txq_limit_mc = IEEE80211_DEFAULT_AQL_TXQ_LIMIT_MC;
for (i = 0; i < IEEE80211_NUM_ACS; i++) {
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 03/11] wifi: cfg80211: support C/B/I/GTK per AP in client modes
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
2026-10-13 12:59 ` [PATCH 01/11] wifi: cfg80211: reject WEP/TKIP with multi-link Johannes Berg
2026-10-13 12:59 ` [PATCH 02/11] wifi: mac80211: replace ieee80211_iter_keys_rcu() Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 04/11] wifi: mac80211: clarify per-STA GTK handling code/docs Johannes Berg
` (7 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
Add WIPHY_FLAG_CLIENT_AP_STA_GTK indicating that the driver
does per-AP C/B/I/GTK in client/P2P-client modes. If this is
set, then cfg80211 will allow userspace to pass the AP's
(or AP MLD's) MAC address with key commands, and will also
pass the current AP's MAC address if not given by userspace
for drivers requiring it, for backward compatibility.
Per-AP C/B/I/GTK for RX will be required by mac80211 to clean
up the code as well as for SMD Transition, where group keys
for the target AP may be installed while the switch isn't
complete yet.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/cfg80211.h | 7 +++++-
net/wireless/core.h | 2 ++
net/wireless/nl80211.c | 12 +++++++++-
net/wireless/rdev-ops.h | 6 +++++
net/wireless/sme.c | 2 ++
net/wireless/util.c | 47 ++++++++++++++++++++++++++++++++++++--
net/wireless/wext-compat.c | 5 ++++
7 files changed, 77 insertions(+), 4 deletions(-)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index f773856c1818..08e340724142 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -5789,6 +5789,10 @@ struct cfg80211_ops {
* set this flag to update channels on beacon hints.
* @WIPHY_FLAG_SUPPORTS_NSTR_NONPRIMARY: support connection to non-primary link
* of an NSTR mobile AP MLD.
+ * @WIPHY_FLAG_CLIENT_AP_STA_GTK: Handle C/B/I/GTK installation on a STA (AP
+ * STA) basis for station and P2P client interfaces.
+ * (Note that if userspace doesn't give the MAC address, the AP's address
+ * is substituted to allow for older userspace versions.)
*/
enum wiphy_flags {
WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK = BIT(0),
@@ -5800,7 +5804,7 @@ enum wiphy_flags {
WIPHY_FLAG_4ADDR_STATION = BIT(6),
WIPHY_FLAG_CONTROL_PORT_PROTOCOL = BIT(7),
WIPHY_FLAG_IBSS_RSN = BIT(8),
- /* reuse bit 9 */
+ WIPHY_FLAG_CLIENT_AP_STA_GTK = BIT(9),
WIPHY_FLAG_MESH_AUTH = BIT(10),
WIPHY_FLAG_SUPPORTS_EXT_KCK_32 = BIT(11),
WIPHY_FLAG_SUPPORTS_NSTR_NONPRIMARY = BIT(12),
@@ -7385,6 +7389,7 @@ struct wireless_dev {
u8 connected_addr[ETH_ALEN] __aligned(2);
u8 ssid[IEEE80211_MAX_SSID_LEN];
u8 ssid_len;
+ bool wep_used;
} client;
struct {
int beacon_interval;
diff --git a/net/wireless/core.h b/net/wireless/core.h
index e70a4f679bf5..7318cec3a956 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -483,6 +483,8 @@ bool cfg80211_cigtk_supported(struct wireless_dev *wdev,
bool cfg80211_valid_key_idx(struct wireless_dev *wdev,
int key_idx, enum nl80211_key_type type,
const u8 *mac_addr);
+const u8 *cfg80211_get_key_mac_addr(struct wireless_dev *wdev, u32 cipher,
+ bool pairwise, const u8 *mac_addr);
int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev,
struct key_params *params, int key_idx,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index d790e204aee3..2e296057b036 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -5565,6 +5565,8 @@ static int nl80211_get_key(struct sk_buff *skb, struct genl_info *info)
if (!rdev->ops->get_key)
return -EOPNOTSUPP;
+ mac_addr = cfg80211_get_key_mac_addr(wdev, 0, pairwise, mac_addr);
+
if (!cfg80211_valid_key_idx(wdev, key_idx, type, mac_addr))
return -ENOENT;
@@ -5766,6 +5768,10 @@ static int nl80211_new_key(struct sk_buff *skb, struct genl_info *info)
if (!rdev->ops->add_key)
return -EOPNOTSUPP;
+ mac_addr = cfg80211_get_key_mac_addr(wdev, key.p.cipher,
+ key.type == NL80211_KEYTYPE_PAIRWISE,
+ mac_addr);
+
if (cfg80211_validate_key_settings(rdev, wdev, &key.p, key.idx,
key.type, mac_addr)) {
GENL_SET_ERR_MSG(info, "key setting validation failed");
@@ -5795,7 +5801,7 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
struct cfg80211_registered_device *rdev = info->user_ptr[0];
int err;
struct wireless_dev *wdev = info->user_ptr[1];
- u8 *mac_addr = NULL;
+ const u8 *mac_addr = NULL;
struct key_parse key;
int link_id = nl80211_link_id_or_invalid(info->attrs);
@@ -5821,6 +5827,10 @@ static int nl80211_del_key(struct sk_buff *skb, struct genl_info *info)
return -EINVAL;
}
+ mac_addr = cfg80211_get_key_mac_addr(wdev, 0,
+ key.type == NL80211_KEYTYPE_PAIRWISE,
+ mac_addr);
+
if (!cfg80211_valid_key_idx(wdev, key.idx, key.type, mac_addr))
return -EINVAL;
diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h
index ae15cec9bf8b..7b8440c1f697 100644
--- a/net/wireless/rdev-ops.h
+++ b/net/wireless/rdev-ops.h
@@ -86,6 +86,12 @@ static inline int rdev_add_key(struct cfg80211_registered_device *rdev,
mac_addr, params->mode);
ret = rdev->ops->add_key(&rdev->wiphy, wdev, link_id, key_index,
type, mac_addr, params);
+ if (!ret &&
+ (wdev->iftype == NL80211_IFTYPE_STATION ||
+ wdev->iftype == NL80211_IFTYPE_P2P_CLIENT) &&
+ (params->cipher == WLAN_CIPHER_SUITE_WEP40 ||
+ params->cipher == WLAN_CIPHER_SUITE_WEP104))
+ wdev->u.client.wep_used = true;
trace_rdev_return_int(&rdev->wiphy, ret);
return ret;
}
diff --git a/net/wireless/sme.c b/net/wireless/sme.c
index fde86c900f4f..861b38eb08f2 100644
--- a/net/wireless/sme.c
+++ b/net/wireless/sme.c
@@ -565,6 +565,7 @@ static int cfg80211_sme_connect(struct wireless_dev *wdev,
if (wdev->connected) {
cfg80211_sme_free(wdev);
wdev->connected = false;
+ wdev->u.client.wep_used = false;
}
if (wdev->conn)
@@ -1360,6 +1361,7 @@ void __cfg80211_disconnected(struct net_device *dev, const u8 *ie,
wdev->valid_links = 0;
wdev->connected = false;
wdev->u.client.ssid_len = 0;
+ wdev->u.client.wep_used = false;
wdev->conn_owner_nlportid = 0;
kfree_sensitive(wdev->connect_keys);
wdev->connect_keys = NULL;
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 59912f4a93c8..ad0e7b279111 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -274,6 +274,39 @@ bool cfg80211_cigtk_supported(struct wireless_dev *wdev,
return false;
}
+const u8 *cfg80211_get_key_mac_addr(struct wireless_dev *wdev, u32 cipher,
+ bool pairwise, const u8 *mac_addr)
+{
+ if (pairwise || mac_addr)
+ return mac_addr;
+
+ if (wdev->iftype != NL80211_IFTYPE_STATION &&
+ wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
+ return NULL;
+
+ /*
+ * For client side, historically the RX B/I/GTKs were set for
+ * the AP without a MAC address parameter, just for the current
+ * AP. With WIPHY_FLAG_CLIENT_AP_STA_GTK this has changed, and
+ * B/I/GTK is set for RX just like other types of interfaces,
+ * with the AP (MLD) address (for MLO the link ID was already
+ * required.)
+ *
+ * For WEP, however, the address is still (required to be) NULL
+ * since WEP keys are used for both TX and RX.
+ */
+
+ if (!(wdev->wiphy->flags & WIPHY_FLAG_CLIENT_AP_STA_GTK))
+ return NULL;
+ if (!wdev->connected)
+ return NULL;
+ if (cipher == WLAN_CIPHER_SUITE_WEP40 ||
+ cipher == WLAN_CIPHER_SUITE_WEP104 ||
+ wdev->u.client.wep_used)
+ return NULL;
+ return wdev->u.client.connected_addr;
+}
+
bool cfg80211_valid_key_idx(struct wireless_dev *wdev,
int key_idx, enum nl80211_key_type type,
const u8 *mac_addr)
@@ -310,7 +343,9 @@ bool cfg80211_valid_key_idx(struct wireless_dev *wdev,
* For group keys, mac_addr==NULL means setting a group key
* for TX, which is only supported on some interface types,
* except for STATION/P2P_CLIENT, where it's setting the RX
- * key with the current AP (for legacy reasons.)
+ * key with the current AP (for legacy reasons). If the
+ * WIPHY_FLAG_CLIENT_AP_STA_GTK flag is set, userspace may
+ * set the group key with address (except for WEP.)
*
* Apart from that exception, a non-NULL mac_addr means RX
* key being set.
@@ -339,8 +374,12 @@ bool cfg80211_valid_key_idx(struct wireless_dev *wdev,
case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT:
/* see note about exception above */
- if (mac_addr)
+ if (mac_addr &&
+ !(wdev->wiphy->flags & WIPHY_FLAG_CLIENT_AP_STA_GTK))
return false;
+ /* MAC address is OK, continue checks without */
+ if (wdev->wiphy->flags & WIPHY_FLAG_CLIENT_AP_STA_GTK)
+ mac_addr = NULL;
/* BIGTK support implies IGTK support */
if (wiphy_ext_feature_isset(wdev->wiphy,
NL80211_EXT_FEATURE_BEACON_PROTECTION_CLIENT))
@@ -419,6 +458,10 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev,
break;
case WLAN_CIPHER_SUITE_WEP40:
case WLAN_CIPHER_SUITE_WEP104:
+ if (mac_addr && !pairwise &&
+ (wdev->iftype == NL80211_IFTYPE_STATION ||
+ wdev->iftype == NL80211_IFTYPE_P2P_CLIENT))
+ return -EINVAL;
if (key_idx > 3)
return -EINVAL;
break;
diff --git a/net/wireless/wext-compat.c b/net/wireless/wext-compat.c
index ec2389f6b8b1..5300c692355a 100644
--- a/net/wireless/wext-compat.c
+++ b/net/wireless/wext-compat.c
@@ -456,6 +456,9 @@ static int cfg80211_set_encryption(struct cfg80211_registered_device *rdev,
rejoin = true;
}
+ addr = cfg80211_get_key_mac_addr(wdev, 0, pairwise,
+ addr);
+
if (!cfg80211_valid_key_idx(wdev, idx, key_type, addr))
err = -ENOENT;
else
@@ -491,6 +494,8 @@ static int cfg80211_set_encryption(struct cfg80211_registered_device *rdev,
if (addr)
tx_key = false;
+ addr = cfg80211_get_key_mac_addr(wdev, params->cipher, pairwise, addr);
+
if (cfg80211_validate_key_settings(rdev, wdev, params, idx,
pairwise, addr))
return -EINVAL;
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 04/11] wifi: mac80211: clarify per-STA GTK handling code/docs
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (2 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 03/11] wifi: cfg80211: support C/B/I/GTK per AP in client modes Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 05/11] wifi: mac80211: use per-STA GTK in client mode Johannes Berg
` (6 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
The FT case in the documentation cannot happen, because
the way mac80211 handles FT (now) doesn't add a station
entry before the actual BSS transition. In addition, the
documentation doesn't mention mesh, which also uses this
flag. NAN was always allowing it, out of necessity.
Also clarify that other interface types aren't supported
(for now) at all, which is already rejected by cfg80211
but checking here is easy and makes the code clearer.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 12 +++++++-----
net/mac80211/key.c | 18 ++++++++++++++----
2 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 2272d7c85dd4..a25f499f3e9f 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2918,11 +2918,13 @@ struct ieee80211_txq {
* dtim_period).
*
* @IEEE80211_HW_SUPPORTS_PER_STA_GTK: The device's crypto engine supports
- * per-station GTKs as used by IBSS RSN or during fast transition. If
- * the device doesn't support per-station GTKs, but can be asked not
- * to decrypt group addressed frames, then IBSS RSN support is still
- * possible but software crypto will be used. Advertise the wiphy flag
- * only in that case.
+ * per-station GTKs as used by IBSS RSN and mesh. If the device doesn't
+ * support per-station GTKs, but can be asked not to decrypt group
+ * addressed frames, then IBSS RSN support is still possible but
+ * software crypto must be used. Drivers advertise the wiphy flag
+ * (%WIPHY_FLAG_IBSS_RSN) instead in that case.
+ * For mesh, it's assumed that either this flag is set or SW crypto
+ * can be used, regardless of additional wiphy flags.
*
* @IEEE80211_HW_AP_LINK_PS: When operating in AP mode the device
* autonomously manages the PS status of connected stations. When
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index b55de973a929..112060c5e2cf 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -155,10 +155,20 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
* per-station GTKs are required to be supported if secure NAN is
* supported).
*/
- if (sta && !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE) &&
- !(ieee80211_hw_check(&key->local->hw, SUPPORTS_PER_STA_GTK) ||
- sdata->vif.type == NL80211_IFTYPE_NAN_DATA))
- goto out_unsupported;
+ if (sta && !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE)) {
+ switch (sdata->vif.type) {
+ case NL80211_IFTYPE_NAN_DATA:
+ break;
+ case NL80211_IFTYPE_ADHOC:
+ case NL80211_IFTYPE_MESH_POINT:
+ if (!(ieee80211_hw_check(&key->local->hw,
+ SUPPORTS_PER_STA_GTK)))
+ goto out_unsupported;
+ break;
+ default:
+ goto out_unsupported;
+ }
+ }
if (sta && !sta->uploaded)
goto out_unsupported;
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 05/11] wifi: mac80211: use per-STA GTK in client mode
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (3 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 04/11] wifi: mac80211: clarify per-STA GTK handling code/docs Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 06/11] wifi: mac80211: allow drivers to install AP GTK on STA Johannes Berg
` (5 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
Historically, client mode installed GTKs per interface,
conflating them with AP mode where this is done, but in
that case they're for TX, while in client they're only
for RX.
Use the new WIPHY_FLAG_CLIENT_AP_STA_GTK cfg80211 flag
to indicate that we want/support per-AP-STA GTKs, and
then rename the gtk[]/cigtk[] arrays to tx_*[] (link)
and to rx_*[] (link station) accordingly, finally
clearing this up.
For now, don't tell the driver about this, though it
would simplify at least iwlwifi.
Note that I've also fixed an RCU issue here: removing
a link station previously didn't remove keys for it,
so they'd be reachable pointing to a freed link STA.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 14 ++-
net/mac80211/ieee80211_i.h | 8 +-
net/mac80211/key.c | 186 +++++++++++++++++++++++++------------
net/mac80211/key.h | 6 ++
net/mac80211/main.c | 3 +-
net/mac80211/rx.c | 80 ++++++----------
net/mac80211/sta_info.c | 19 ++++
net/mac80211/sta_info.h | 15 +--
8 files changed, 201 insertions(+), 130 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 9608c07bcea7..b6d759f38c6c 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -704,6 +704,11 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
switch (sdata->vif.type) {
case NL80211_IFTYPE_STATION:
+ /* mostly handled by cfg80211, but make sure */
+ if (pairwise && !mac_addr) {
+ ieee80211_key_free_unused(key);
+ return -EINVAL;
+ }
if (sdata->u.mgd.mfp != IEEE80211_MFP_DISABLED)
key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
break;
@@ -786,14 +791,14 @@ ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata, int link_id,
if (cigtk && key_idx < NUM_CTRL_KEYS)
return wiphy_dereference(local->hw.wiphy,
- link_sta->cigtk[key_idx]);
+ link_sta->rx_cigtk[key_idx]);
if (!pairwise && !cigtk &&
key_idx < NUM_DEFAULT_KEYS +
NUM_DEFAULT_MGMT_KEYS +
NUM_DEFAULT_BEACON_KEYS)
return wiphy_dereference(local->hw.wiphy,
- link_sta->gtk[key_idx]);
+ link_sta->rx_gtk[key_idx]);
return NULL;
}
@@ -802,9 +807,10 @@ ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata, int link_id,
return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]);
if (cigtk)
- return wiphy_dereference(local->hw.wiphy, link->cigtk[key_idx]);
+ return wiphy_dereference(local->hw.wiphy,
+ link->tx_cigtk[key_idx]);
- key = wiphy_dereference(local->hw.wiphy, link->gtk[key_idx]);
+ key = wiphy_dereference(local->hw.wiphy, link->tx_gtk[key_idx]);
if (key)
return key;
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 52e413a20f71..56ebe73f1bb8 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1114,10 +1114,10 @@ struct ieee80211_link_data {
unsigned int link_id;
/* multicast keys only */
- struct ieee80211_key __rcu *gtk[NUM_DEFAULT_KEYS +
- NUM_DEFAULT_MGMT_KEYS +
- NUM_DEFAULT_BEACON_KEYS];
- struct ieee80211_key __rcu *cigtk[NUM_CTRL_KEYS];
+ struct ieee80211_key __rcu *tx_gtk[NUM_DEFAULT_KEYS +
+ NUM_DEFAULT_MGMT_KEYS +
+ NUM_DEFAULT_BEACON_KEYS];
+ struct ieee80211_key __rcu *tx_cigtk[NUM_CTRL_KEYS];
struct ieee80211_key __rcu *default_multicast_key;
struct ieee80211_key __rcu *default_mgmt_key;
struct ieee80211_key __rcu *default_beacon_key;
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index 112060c5e2cf..d4d66d249719 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -22,6 +22,7 @@
#include "ieee80211_i.h"
#include "driver-ops.h"
#include "debugfs_key.h"
+#include "sta_info.h"
#include "aes_ccm.h"
#include "aes_cmac.h"
#include "aes_gmac.h"
@@ -165,6 +166,10 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
SUPPORTS_PER_STA_GTK)))
goto out_unsupported;
break;
+ case NL80211_IFTYPE_STATION:
+ /* install group keys w/o STA as we always did */
+ sta = NULL;
+ break;
default:
goto out_unsupported;
}
@@ -245,6 +250,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
{
struct ieee80211_sub_if_data *sdata;
+ struct ieee80211_sta *pubsta;
struct sta_info *sta;
int ret;
@@ -270,15 +276,20 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
IEEE80211_KEY_FLAG_RESERVE_TAILROOM)))
increment_tailroom_need_count(sdata);
+ pubsta = sta ? &sta->sta : NULL;
+ /* for now override STA to NULL for client group keys as before */
+ if (sdata->vif.type == NL80211_IFTYPE_STATION &&
+ !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE))
+ pubsta = NULL;
+
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
- ret = drv_set_key(key->local, DISABLE_KEY, sdata,
- sta ? &sta->sta : NULL, &key->conf);
+ ret = drv_set_key(key->local, DISABLE_KEY, sdata, pubsta, &key->conf);
if (ret)
sdata_err(sdata,
"failed to remove key (%d, %pM) from hardware (%d)\n",
key->conf.keyidx,
- sta ? sta->sta.addr : bcast_addr, ret);
+ pubsta ? pubsta->addr : bcast_addr, ret);
}
static int _ieee80211_set_tx_key(struct ieee80211_key *key, bool force)
@@ -374,7 +385,7 @@ static void __ieee80211_set_default_key(struct ieee80211_link_data *link,
sdata->keys[idx]);
if (!key)
key = wiphy_dereference(sdata->local->hw.wiphy,
- link->gtk[idx]);
+ link->tx_gtk[idx]);
}
if (uni) {
@@ -409,7 +420,7 @@ __ieee80211_set_default_mgmt_key(struct ieee80211_link_data *link, int idx)
if (idx >= NUM_DEFAULT_KEYS &&
idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS)
key = wiphy_dereference(sdata->local->hw.wiphy,
- link->gtk[idx]);
+ link->tx_gtk[idx]);
rcu_assign_pointer(link->default_mgmt_key, key);
@@ -436,7 +447,7 @@ __ieee80211_set_default_beacon_key(struct ieee80211_link_data *link, int idx)
idx < NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS +
NUM_DEFAULT_BEACON_KEYS)
key = wiphy_dereference(sdata->local->hw.wiphy,
- link->gtk[idx]);
+ link->tx_gtk[idx]);
rcu_assign_pointer(link->default_beacon_key, key);
@@ -454,11 +465,11 @@ void ieee80211_set_default_beacon_key(struct ieee80211_link_data *link,
static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
struct ieee80211_link_data *link,
struct sta_info *sta,
+ struct link_sta_info *link_sta,
enum ieee80211_key_flags flags,
struct ieee80211_key *old,
struct ieee80211_key *new)
{
- struct link_sta_info *link_sta = sta ? &sta->deflink : NULL;
bool pairwise = flags & IEEE80211_KEY_FLAG_PAIRWISE;
bool cip = flags & IEEE80211_KEY_FLAG_CIP;
int link_id;
@@ -497,7 +508,7 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
return -ENOLINK;
}
- if (sta) {
+ if (sta && !link_sta) {
link_sta = rcu_dereference_protected(sta->link[link_id],
lockdep_is_held(&sta->local->hw.wiphy->mtx));
if (!link_sta)
@@ -505,6 +516,7 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
}
} else {
link = &sdata->deflink;
+ link_sta = sta ? &sta->deflink : NULL;
}
if ((is_wep || pairwise) && idx >= NUM_DEFAULT_KEYS)
@@ -555,9 +567,9 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
!(new->conf.flags & IEEE80211_KEY_FLAG_NO_AUTO_TX))
_ieee80211_set_tx_key(new, true);
} else if (cip) {
- rcu_assign_pointer(link_sta->cigtk[idx], new);
+ rcu_assign_pointer(link_sta->rx_cigtk[idx], new);
} else {
- rcu_assign_pointer(link_sta->gtk[idx], new);
+ rcu_assign_pointer(link_sta->rx_gtk[idx], new);
}
/* Only needed for transition from no key -> key.
* Still triggers unnecessary when using Extended Key ID
@@ -591,9 +603,9 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
if (is_wep || pairwise)
rcu_assign_pointer(sdata->keys[idx], new);
else if (cip)
- rcu_assign_pointer(link->cigtk[idx], new);
+ rcu_assign_pointer(link->tx_cigtk[idx], new);
else
- rcu_assign_pointer(link->gtk[idx], new);
+ rcu_assign_pointer(link->tx_gtk[idx], new);
if (defunikey && new)
__ieee80211_set_default_key(link, new->conf.keyidx,
@@ -790,7 +802,7 @@ static void ieee80211_key_free_common(struct ieee80211_key *key)
kfree_sensitive(key);
}
-static void __ieee80211_key_destroy(struct ieee80211_key *key,
+static void __ieee80211_key_cleanup(struct ieee80211_key *key,
bool delay_tailroom)
{
if (key->local) {
@@ -808,6 +820,12 @@ static void __ieee80211_key_destroy(struct ieee80211_key *key,
decrease_tailroom_need_count(sdata, 1);
}
}
+}
+
+static void __ieee80211_key_destroy(struct ieee80211_key *key,
+ bool delay_tailroom)
+{
+ __ieee80211_key_cleanup(key, delay_tailroom);
ieee80211_key_free_common(key);
}
@@ -928,14 +946,14 @@ int ieee80211_key_link(struct ieee80211_key *key,
if (key->conf.flags & IEEE80211_KEY_FLAG_CIP)
old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link_sta->cigtk[idx]);
+ link_sta->rx_cigtk[idx]);
else
old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link_sta->gtk[idx]);
+ link_sta->rx_gtk[idx]);
} else {
if (key->conf.flags & IEEE80211_KEY_FLAG_CIP) {
old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link->cigtk[idx]);
+ link->tx_cigtk[idx]);
} else {
if (idx < NUM_DEFAULT_KEYS)
old_key = wiphy_dereference(sdata->local->hw.wiphy,
@@ -943,7 +961,7 @@ int ieee80211_key_link(struct ieee80211_key *key,
if (!old_key)
old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link->gtk[idx]);
+ link->tx_gtk[idx]);
}
}
@@ -975,7 +993,7 @@ int ieee80211_key_link(struct ieee80211_key *key,
key->color = atomic_inc_return(&key_color);
/* keep this flag for easier access later */
- if (sta && sta->sta.spp_amsdu)
+ if (pairwise && sta && sta->sta.spp_amsdu)
key->conf.flags |= IEEE80211_KEY_FLAG_SPP_AMSDU;
/* A CIP related key must be GCMP-256 (really GMAC-256) */
@@ -987,8 +1005,8 @@ int ieee80211_key_link(struct ieee80211_key *key,
increment_tailroom_need_count(sdata);
- ret = ieee80211_key_replace(sdata, link, sta, key->conf.flags,
- old_key, key);
+ ret = ieee80211_key_replace(sdata, link, sta, NULL,
+ key->conf.flags, old_key, key);
if (!ret) {
ieee80211_debugfs_key_add(key);
@@ -1013,7 +1031,7 @@ void ieee80211_key_free(struct ieee80211_key *key, bool delay_tailroom)
* Replace key with nothingness if it was ever used.
*/
if (key->sdata)
- ieee80211_key_replace(key->sdata, NULL, key->sta,
+ ieee80211_key_replace(key->sdata, NULL, key->sta, NULL,
key->conf.flags, key, NULL);
ieee80211_key_destroy(key, delay_tailroom);
}
@@ -1055,13 +1073,21 @@ ieee80211_key_iter(struct ieee80211_hw *hw,
void *data),
void *iter_data)
{
- /* skip keys of station in removal process */
- if (key->sta && key->sta->removed)
- return;
+ struct sta_info *sta = key->sta;
+
if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
return;
- iter(hw, vif, key->sta ? &key->sta->sta : NULL,
- &key->conf, iter_data);
+
+ /* for now override STA to NULL for client group keys as before */
+ if (vif->type == NL80211_IFTYPE_STATION &&
+ !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE))
+ sta = NULL;
+
+ /* skip keys of station in removal process (if driver knew about STA) */
+ if (sta && sta->removed)
+ return;
+
+ iter(hw, vif, sta ? &sta->sta : NULL, &key->conf, iter_data);
}
void ieee80211_iter_keys(struct ieee80211_hw *hw,
@@ -1148,7 +1174,7 @@ static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata,
ieee80211_debugfs_key_remove_beacon_default(sdata);
list_for_each_entry_safe(key, tmp, &sdata->key_list, list) {
- ieee80211_key_replace(key->sdata, NULL, key->sta,
+ ieee80211_key_replace(key->sdata, NULL, key->sta, NULL,
key->conf.flags, key, NULL);
list_add_tail(&key->list, keys);
}
@@ -1168,7 +1194,7 @@ void ieee80211_remove_link_keys(struct ieee80211_link_data *link,
list_for_each_entry_safe(key, tmp, &sdata->key_list, list) {
if (key->conf.link_id != link->link_id)
continue;
- ieee80211_key_replace(key->sdata, link, key->sta,
+ ieee80211_key_replace(key->sdata, link, key->sta, NULL,
key->conf.flags, key, NULL);
list_add_tail(&key->list, keys);
}
@@ -1232,6 +1258,54 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata,
}
}
+void ieee80211_unlink_link_sta_keys(struct ieee80211_local *local,
+ struct link_sta_info *link_sta)
+{
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ for (int i = 0; i < ARRAY_SIZE(link_sta->rx_gtk); i++) {
+ struct ieee80211_key *key;
+ int ret;
+
+ key = wiphy_dereference(local->hw.wiphy, link_sta->rx_gtk[i]);
+ if (!key)
+ continue;
+ ret = ieee80211_key_replace(key->sdata, NULL, key->sta, link_sta,
+ key->conf.flags, key, NULL);
+ if (WARN(ret, "failed to remove STA link key (%d)\n", ret))
+ continue;
+ list_add(&key->list, &link_sta->key_destroy_list);
+ __ieee80211_key_cleanup(key, key->sdata->vif.type ==
+ NL80211_IFTYPE_STATION);
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(link_sta->rx_cigtk); i++) {
+ struct ieee80211_key *key;
+ int ret;
+
+ key = wiphy_dereference(local->hw.wiphy, link_sta->rx_cigtk[i]);
+ if (!key)
+ continue;
+ ret = ieee80211_key_replace(key->sdata, NULL, key->sta, link_sta,
+ key->conf.flags, key, NULL);
+ if (WARN(ret, "failed to remove STA link key (%d)\n", ret))
+ continue;
+ list_add(&key->list, &link_sta->key_destroy_list);
+ __ieee80211_key_cleanup(key, key->sdata->vif.type ==
+ NL80211_IFTYPE_STATION);
+ }
+}
+
+void ieee80211_free_link_sta_keys(struct link_sta_info *link_sta)
+{
+ struct ieee80211_key *key, *tmp;
+
+ list_for_each_entry_safe(key, tmp, &link_sta->key_destroy_list, list) {
+ list_del(&key->list);
+ ieee80211_key_free_common(key);
+ }
+}
+
void ieee80211_free_sta_keys(struct ieee80211_local *local,
struct sta_info *sta)
{
@@ -1240,31 +1314,11 @@ void ieee80211_free_sta_keys(struct ieee80211_local *local,
lockdep_assert_wiphy(local->hw.wiphy);
- for (i = 0; i < ARRAY_SIZE(sta->deflink.gtk); i++) {
- key = wiphy_dereference(local->hw.wiphy, sta->deflink.gtk[i]);
- if (!key)
- continue;
- ieee80211_key_replace(key->sdata, NULL, key->sta,
- key->conf.flags, key, NULL);
- __ieee80211_key_destroy(key, key->sdata->vif.type ==
- NL80211_IFTYPE_STATION);
- }
-
- for (i = 0; i < ARRAY_SIZE(sta->deflink.cigtk); i++) {
- key = wiphy_dereference(local->hw.wiphy, sta->deflink.cigtk[i]);
- if (!key)
- continue;
- ieee80211_key_replace(key->sdata, NULL, key->sta,
- key->conf.flags, key, NULL);
- __ieee80211_key_destroy(key, key->sdata->vif.type ==
- NL80211_IFTYPE_STATION);
- }
-
for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
key = wiphy_dereference(local->hw.wiphy, sta->ptk[i]);
if (!key)
continue;
- ieee80211_key_replace(key->sdata, NULL, key->sta,
+ ieee80211_key_replace(key->sdata, NULL, key->sta, NULL,
key->conf.flags, key, NULL);
__ieee80211_key_destroy(key, key->sdata->vif.type ==
NL80211_IFTYPE_STATION);
@@ -1427,8 +1481,10 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
struct ieee80211_local *local = sdata->local;
+ struct link_sta_info *link_sta;
struct ieee80211_key *prev_key;
struct ieee80211_key *key;
+ struct sta_info *sta;
int err;
struct ieee80211_link_data *link_data =
link_id < 0 ? &sdata->deflink :
@@ -1450,29 +1506,43 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
if (WARN_ON(cigtk && idx >= NUM_CTRL_KEYS))
return ERR_PTR(-EINVAL);
+ sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr);
+ if (WARN_ON(!sta))
+ return ERR_PTR(-ENOENT);
+
+ if (link_id < 0) {
+ link_sta = &sta->deflink;
+ } else {
+ link_sta = wiphy_dereference(local->hw.wiphy,
+ sta->link[link_id]);
+ if (WARN_ON(!link_sta))
+ return ERR_PTR(-ENOLINK);
+ }
+
if (cigtk)
prev_key = wiphy_dereference(local->hw.wiphy,
- link_data->cigtk[idx]);
+ link_sta->rx_cigtk[idx]);
else
prev_key = wiphy_dereference(local->hw.wiphy,
- link_data->gtk[idx]);
+ link_sta->rx_gtk[idx]);
+
if (!prev_key) {
if (cigtk) {
prev_key = wiphy_dereference(local->hw.wiphy,
- link_data->cigtk[idx ^ 1]);
+ link_sta->rx_cigtk[idx ^ 1]);
} else if (idx < NUM_DEFAULT_KEYS) {
for (int i = 0; i < NUM_DEFAULT_KEYS; i++) {
if (i == idx)
continue;
prev_key = wiphy_dereference(local->hw.wiphy,
- link_data->gtk[i]);
+ link_sta->rx_gtk[i]);
if (prev_key)
break;
}
} else {
/* For IGTK we have 4 and 5 and for BIGTK - 6 and 7 */
prev_key = wiphy_dereference(local->hw.wiphy,
- link_data->gtk[idx ^ 1]);
+ link_sta->rx_gtk[idx ^ 1]);
}
}
@@ -1496,7 +1566,7 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
key->conf.link_id = link_data->link_id;
- err = ieee80211_key_link(key, link_data, NULL);
+ err = ieee80211_key_link(key, link_data, sta);
if (err)
return ERR_PTR(err);
@@ -1565,9 +1635,6 @@ int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata,
!(del_links_mask & BIT(key->conf.link_id)))
continue;
- /* shouldn't happen for per-link keys */
- WARN_ON(key->sta);
-
ieee80211_key_disable_hw_accel(key);
}
@@ -1576,9 +1643,6 @@ int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata,
!(add_links_mask & BIT(key->conf.link_id)))
continue;
- /* shouldn't happen for per-link keys */
- WARN_ON(key->sta);
-
ret = ieee80211_key_enable_hw_accel(key);
if (ret)
return ret;
diff --git a/net/mac80211/key.h b/net/mac80211/key.h
index c8531b516c1c..db86bdc5744f 100644
--- a/net/mac80211/key.h
+++ b/net/mac80211/key.h
@@ -138,6 +138,9 @@ struct ieee80211_key {
struct ieee80211_key_conf conf;
};
+/* from sta_info.h, but cannot include that */
+struct link_sta_info;
+
struct ieee80211_key *
ieee80211_key_alloc(u32 cipher, int idx, size_t key_len,
const u8 *key_data,
@@ -164,6 +167,9 @@ void ieee80211_free_key_list(struct ieee80211_local *local,
struct list_head *keys);
void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata,
bool force_synchronize);
+void ieee80211_unlink_link_sta_keys(struct ieee80211_local *local,
+ struct link_sta_info *link_sta);
+void ieee80211_free_link_sta_keys(struct link_sta_info *link_sta);
void ieee80211_free_sta_keys(struct ieee80211_local *local,
struct sta_info *sta);
void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 2d60fe5b3f91..2084f7f554ad 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -868,7 +868,8 @@ struct ieee80211_hw *ieee80211_alloc_hw_nm(size_t priv_data_len,
WIPHY_FLAG_4ADDR_AP |
WIPHY_FLAG_4ADDR_STATION |
WIPHY_FLAG_REPORTS_OBSS |
- WIPHY_FLAG_OFFCHAN_TX;
+ WIPHY_FLAG_OFFCHAN_TX |
+ WIPHY_FLAG_CLIENT_AP_STA_GTK;
if (emulate_chanctx || ops->remain_on_channel)
wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index ecee9348c70d..905fd70491ce 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1969,37 +1969,34 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
} /* ieee80211_rx_h_sta_process */
static struct ieee80211_key *
-ieee80211_rx_get_bigtk(struct ieee80211_rx_data *rx, int idx)
+ieee80211_rx_get_igtk_bigtk(struct ieee80211_rx_data *rx, bool beacon, int idx)
{
- struct ieee80211_key *key = NULL;
+ struct ieee80211_key *key;
int idx2;
- /* Make sure key gets set if either BIGTK key index is set so that
+ /* Make sure key gets set if either (B)IGTK key index is set so that
* ieee80211_drop_unencrypted_mgmt() can properly drop both unprotected
- * Beacon frames and Beacon frames that claim to use another BIGTK key
+ * mgmt/beacon frames and frames that claim to use another (B)IGTK key
* index (i.e., a key that we do not have).
*/
if (idx < 0) {
- idx = NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS;
+ idx = NUM_DEFAULT_KEYS + beacon * NUM_DEFAULT_MGMT_KEYS;
idx2 = idx + 1;
} else {
- if (idx == NUM_DEFAULT_KEYS + NUM_DEFAULT_MGMT_KEYS)
+ if (idx == NUM_DEFAULT_KEYS + beacon * NUM_DEFAULT_MGMT_KEYS)
idx2 = idx + 1;
else
idx2 = idx - 1;
}
- if (rx->link_sta)
- key = rcu_dereference(rx->link_sta->gtk[idx]);
- if (!key)
- key = rcu_dereference(rx->link->gtk[idx]);
- if (!key && rx->link_sta)
- key = rcu_dereference(rx->link_sta->gtk[idx2]);
- if (!key)
- key = rcu_dereference(rx->link->gtk[idx2]);
+ if (!rx->link_sta)
+ return NULL;
- return key;
+ key = rcu_dereference(rx->link_sta->rx_gtk[idx]);
+ if (key)
+ return key;
+ return rcu_dereference(rx->link_sta->rx_gtk[idx2]);
}
static ieee80211_rx_result debug_noinline
@@ -2092,7 +2089,7 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx)
return RX_DROP_U_BAD_BCN_KEYIDX;
}
- rx->key = ieee80211_rx_get_bigtk(rx, mmie_keyidx);
+ rx->key = ieee80211_rx_get_igtk_bigtk(rx, true, mmie_keyidx);
if (!rx->key)
return RX_CONTINUE; /* Beacon protection not in use */
} else if (mmie_keyidx >= 0) {
@@ -2109,10 +2106,8 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx)
test_sta_flag(rx->sta, WLAN_STA_MFP))
return RX_DROP_U_UNPROTECTED;
- rx->key = rcu_dereference(rx->link_sta->gtk[mmie_keyidx]);
+ rx->key = rcu_dereference(rx->link_sta->rx_gtk[mmie_keyidx]);
}
- if (!rx->key)
- rx->key = rcu_dereference(rx->link->gtk[mmie_keyidx]);
} else if (!ieee80211_has_protected(fc)) {
/*
* The frame was not protected, so skip decryption. However, we
@@ -2124,24 +2119,15 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx)
int i;
if (ieee80211_is_beacon(fc)) {
- key = ieee80211_rx_get_bigtk(rx, -1);
+ key = ieee80211_rx_get_igtk_bigtk(rx, true, -1);
} else if (ieee80211_is_mgmt(fc) &&
is_multicast_ether_addr(hdr->addr1)) {
- key = rcu_dereference(rx->link->default_mgmt_key);
- } else {
- if (rx->link_sta) {
- for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
- key = rcu_dereference(rx->link_sta->gtk[i]);
- if (key)
- break;
- }
- }
- if (!key) {
- for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
- key = rcu_dereference(rx->link->gtk[i]);
- if (key)
- break;
- }
+ key = ieee80211_rx_get_igtk_bigtk(rx, false, -1);
+ } else if (rx->link_sta) {
+ for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
+ key = rcu_dereference(rx->link_sta->rx_gtk[i]);
+ if (key)
+ break;
}
}
if (key)
@@ -2168,26 +2154,11 @@ ieee80211_rx_h_decrypt(struct ieee80211_rx_data *rx)
/* check per-station GTK first, if multicast packet */
if (is_multicast_ether_addr(hdr->addr1) && rx->link_sta)
- rx->key = rcu_dereference(rx->link_sta->gtk[keyidx]);
+ rx->key = rcu_dereference(rx->link_sta->rx_gtk[keyidx]);
- /* if not found, try default key */
- if (!rx->key) {
- if (is_multicast_ether_addr(hdr->addr1))
- rx->key = rcu_dereference(rx->link->gtk[keyidx]);
- if (!rx->key)
- rx->key = rcu_dereference(rx->sdata->keys[keyidx]);
-
- /*
- * RSNA-protected unicast frames should always be
- * sent with pairwise or station-to-station keys,
- * but for WEP we allow using a key index as well.
- */
- if (rx->key &&
- rx->key->conf.cipher != WLAN_CIPHER_SUITE_WEP40 &&
- rx->key->conf.cipher != WLAN_CIPHER_SUITE_WEP104 &&
- !is_multicast_ether_addr(hdr->addr1))
- rx->key = NULL;
- }
+ /* if not found, try WEP/WPA-NONE */
+ if (!rx->key)
+ rx->key = rcu_dereference(rx->sdata->keys[keyidx]);
}
if (rx->key) {
@@ -4854,6 +4825,7 @@ void ieee80211_check_fast_rx(struct sta_info *sta)
rcu_read_lock();
key = rcu_dereference(sta->ptk[sta->ptk_idx]);
+ /* check WEP key to not erroneously consider it as no encryption */
if (!key)
key = rcu_dereference(sdata->default_unicast_key);
if (key) {
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index 5c95ffee257d..790997592d8c 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -353,6 +353,7 @@ struct sta_info *sta_info_get_by_idx(struct ieee80211_sub_if_data *sdata,
static void sta_info_free_link(struct link_sta_info *link_sta)
{
free_percpu(link_sta->pcpu_rx_stats);
+ ieee80211_free_link_sta_keys(link_sta);
}
static void sta_link_free_rcu(struct rcu_head *head)
@@ -560,6 +561,8 @@ static int sta_info_alloc_link(struct ieee80211_local *local,
struct ieee80211_hw *hw = &local->hw;
int i;
+ INIT_LIST_HEAD(&link_info->key_destroy_list);
+
if (ieee80211_hw_check(hw, USES_RSS)) {
link_info->pcpu_rx_stats =
alloc_percpu_gfp(struct ieee80211_sta_rx_stats, gfp);
@@ -1575,6 +1578,17 @@ static void __sta_info_destroy_part2(struct sta_info *sta, bool recalc)
}
/* now keys can no longer be reached */
+ for (int i = 0; i < ARRAY_SIZE(sta->link); i++) {
+ struct link_sta_info *link_sta;
+
+ link_sta = wiphy_dereference(local->hw.wiphy, sta->link[i]);
+ if (!link_sta)
+ continue;
+
+ ieee80211_unlink_link_sta_keys(local, link_sta);
+ ieee80211_free_link_sta_keys(link_sta);
+ }
+
ieee80211_free_sta_keys(local, sta);
/* disable TIM bit - last chance to tell driver */
@@ -3513,11 +3527,16 @@ int ieee80211_sta_activate_link(struct sta_info *sta, unsigned int link_id)
void ieee80211_sta_remove_link(struct sta_info *sta, unsigned int link_id)
{
+ struct link_sta_info *link_sta = wiphy_dereference(sta->local->hw.wiphy,
+ sta->link[link_id]);
struct ieee80211_sub_if_data *sdata = sta->sdata;
u16 old_links = sta->sta.valid_links;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ if (!WARN_ON(!link_sta))
+ ieee80211_unlink_link_sta_keys(sta->local, link_sta);
+
sta->sta.valid_links &= ~BIT(link_id);
if (!WARN_ON(!test_sta_flag(sta, WLAN_STA_INSERTED)))
diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h
index 8942541375b2..27f31fd7258e 100644
--- a/net/mac80211/sta_info.h
+++ b/net/mac80211/sta_info.h
@@ -484,8 +484,8 @@ struct ieee80211_fragment_cache {
* @capa_nss: NSS limit as determined by local and peer capabilities
* @link_hash_node: hash node for rhashtable
* @sta: Points to the STA info
- * @gtk: group keys negotiated with this station, if any
- * @cigtk: control integrity group keys negotiated with this station, if any
+ * @rx_gtk: group keys negotiated with this station, if any
+ * @rx_cigtk: control integrity group keys negotiated with this station, if any
* @tx_stats: TX statistics
* @tx_stats.packets: # of packets transmitted
* @tx_stats.bytes: # of bytes in all packets transmitted
@@ -522,6 +522,7 @@ struct ieee80211_fragment_cache {
* @uhr_dbe_enabled: for STAs as clients to an AP interface indicates
* DBE is enabled by the STA
* @debugfs_dir: debug filesystem directory dentry
+ * @key_destroy_list: used during removal, keys to destroy
* @pub: public (driver visible) link STA data
*/
struct link_sta_info {
@@ -533,10 +534,10 @@ struct link_sta_info {
struct rhlist_head link_hash_node;
struct sta_info *sta;
- struct ieee80211_key __rcu *gtk[NUM_DEFAULT_KEYS +
- NUM_DEFAULT_MGMT_KEYS +
- NUM_DEFAULT_BEACON_KEYS];
- struct ieee80211_key __rcu *cigtk[NUM_CTRL_KEYS];
+ struct ieee80211_key __rcu *rx_gtk[NUM_DEFAULT_KEYS +
+ NUM_DEFAULT_MGMT_KEYS +
+ NUM_DEFAULT_BEACON_KEYS];
+ struct ieee80211_key __rcu *rx_cigtk[NUM_CTRL_KEYS];
struct ieee80211_sta_rx_stats __percpu *pcpu_rx_stats;
/* Updated from RX path only, no locking requirements */
@@ -581,6 +582,8 @@ struct link_sta_info {
struct dentry *debugfs_dir;
#endif
+ struct list_head key_destroy_list;
+
struct ieee80211_link_sta *pub;
};
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 06/11] wifi: mac80211: allow drivers to install AP GTK on STA
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (4 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 05/11] wifi: mac80211: use per-STA GTK in client mode Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 07/11] wifi: mac80211: remove lookup_key() dead code Johannes Berg
` (4 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
Some drivers (e.g. iwlwifi) may want to install the GTK
on the AP station and not on the interface, so need the
station pointer. Allow them to set a flag to request it
to be passed.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
include/net/mac80211.h | 4 ++++
net/mac80211/debugfs.c | 3 ++-
net/mac80211/key.c | 19 +++++++++++++------
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index a25f499f3e9f..0b759697bcfc 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -3097,6 +3097,9 @@ struct ieee80211_txq {
* @IEEE80211_HW_SUPPORTS_NDP_BLOCKACK: HW can transmit/receive S1G NDP
* BlockAck frames.
*
+ * @IEEE80211_HW_PER_STA_AP_GTK: Install GTKs to the AP STA entry instead
+ * of with %NULL sta.
+ *
* @NUM_IEEE80211_HW_FLAGS: number of hardware flags, used for sizing arrays
*/
enum ieee80211_hw_flags {
@@ -3158,6 +3161,7 @@ enum ieee80211_hw_flags {
IEEE80211_HW_HANDLES_QUIET_CSA,
IEEE80211_HW_STRICT,
IEEE80211_HW_SUPPORTS_NDP_BLOCKACK,
+ IEEE80211_HW_PER_STA_AP_GTK,
/* keep last, obviously */
NUM_IEEE80211_HW_FLAGS
diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c
index a8f677cfc77e..027db8b9f846 100644
--- a/net/mac80211/debugfs.c
+++ b/net/mac80211/debugfs.c
@@ -4,7 +4,7 @@
*
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2013-2014 Intel Mobile Communications GmbH
- * Copyright (C) 2018 - 2019, 2021-2025 Intel Corporation
+ * Copyright (C) 2018 - 2019, 2021-2026 Intel Corporation
*/
#include <linux/debugfs.h>
@@ -462,6 +462,7 @@ static const char *hw_flag_names[] = {
FLAG(HANDLES_QUIET_CSA),
FLAG(STRICT),
FLAG(SUPPORTS_NDP_BLOCKACK),
+ FLAG(PER_STA_AP_GTK),
#undef FLAG
};
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index d4d66d249719..35f44a856bd3 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -167,8 +167,13 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
goto out_unsupported;
break;
case NL80211_IFTYPE_STATION:
- /* install group keys w/o STA as we always did */
- sta = NULL;
+ /*
+ * unless requested otherwise, NULL out
+ * the STA for client group keys
+ */
+ if (!ieee80211_hw_check(&key->local->hw,
+ PER_STA_AP_GTK))
+ sta = NULL;
break;
default:
goto out_unsupported;
@@ -277,9 +282,10 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
increment_tailroom_need_count(sdata);
pubsta = sta ? &sta->sta : NULL;
- /* for now override STA to NULL for client group keys as before */
+ /* unless requested otherwise, NULL out the STA for client group keys */
if (sdata->vif.type == NL80211_IFTYPE_STATION &&
- !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE))
+ !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE) &&
+ !ieee80211_hw_check(&key->local->hw, PER_STA_AP_GTK))
pubsta = NULL;
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
@@ -1078,9 +1084,10 @@ ieee80211_key_iter(struct ieee80211_hw *hw,
if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
return;
- /* for now override STA to NULL for client group keys as before */
+ /* unless requested otherwise, NULL out the STA for client group keys */
if (vif->type == NL80211_IFTYPE_STATION &&
- !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE))
+ !(key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE) &&
+ !ieee80211_hw_check(&key->local->hw, PER_STA_AP_GTK))
sta = NULL;
/* skip keys of station in removal process (if driver knew about STA) */
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 07/11] wifi: mac80211: remove lookup_key() dead code
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (5 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 06/11] wifi: mac80211: allow drivers to install AP GTK on STA Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 08/11] wifi: mac80211: unify key finding/installation Johannes Berg
` (3 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
This code path can never be exercised because pairwise
requires mac_addr set, so the code exited prior to it
being reached here.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 3 ---
1 file changed, 3 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index b6d759f38c6c..a8d6ff1af717 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -803,9 +803,6 @@ ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata, int link_id,
return NULL;
}
- if (pairwise && key_idx < NUM_DEFAULT_KEYS)
- return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]);
-
if (cigtk)
return wiphy_dereference(local->hw.wiphy,
link->tx_cigtk[key_idx]);
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 08/11] wifi: mac80211: unify key finding/installation
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (6 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 07/11] wifi: mac80211: remove lookup_key() dead code Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 09/11] wifi: mac80211: refactor tailroom needed checks Johannes Berg
` (2 subsequent siblings)
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
There are multiple key lookups in the code now:
- ieee80211_lookup_key() for get/del,
- the old key in ieee80211_key_replace(), and
- the new key place in ieee80211_key_link().
Introduce a new ieee80211_key_slot_lookup() function that
looks up the right place to install the key at (and get
the old key or information if needed), so that we can then
pass around that information to the functions needing it,
instead of doing the same kind of logic again and again.
There's one special case in it: if the cipher suite isn't
known (given as zero) then this is a case of looking up an
existing key for get/del, in which case it considers both
the TX GTKs and WEP keys.
Adjust all the code to use the new function throughout,
which in particular requires updates to deleting keys on
interface deletion, but at that point all keys except for
keys specific to the interface (or deflink) should already
be deleted.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/cfg.c | 189 +++++++--------------
net/mac80211/key.c | 404 ++++++++++++++++++++++++++-------------------
net/mac80211/key.h | 47 +++++-
3 files changed, 342 insertions(+), 298 deletions(-)
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index a8d6ff1af717..5ace9ee92c09 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -621,12 +621,10 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
const u8 *mac_addr, struct key_params *params)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
- struct ieee80211_link_data *link =
- ieee80211_link_or_deflink(sdata, link_id, false);
bool pairwise = type == NL80211_KEYTYPE_PAIRWISE;
bool cigtk = type == NL80211_KEYTYPE_CIGTK;
struct ieee80211_local *local = sdata->local;
- struct sta_info *sta = NULL;
+ struct ieee80211_key_slot slot;
struct ieee80211_key *key;
int err;
@@ -635,9 +633,6 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
if (!ieee80211_sdata_running(sdata))
return -ENETDOWN;
- if (IS_ERR(link))
- return PTR_ERR(link);
-
if (WARN_ON(pairwise && link_id >= 0))
return -EINVAL;
@@ -658,6 +653,27 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
break;
}
+ err = ieee80211_key_slot_lookup(sdata, link_id, key_idx, params->cipher,
+ type, mac_addr, &slot);
+ if (err)
+ return err;
+ if (WARN_ON(!slot.key))
+ return -EINVAL;
+
+ /*
+ * The ASSOC test makes sure the driver is ready to receive the key.
+ * When wpa_supplicant has roamed using FT, it attempts to set the
+ * key before association has completed, this rejects that attempt
+ * so it will set the key again after association.
+ *
+ * With (re)association frame encryption enabled, wpa_supplicant may
+ * deliver keys to mac80211 before the station has associated. Accept
+ * that if the station is an Enhanced Privacy Protection (EPP) peer.
+ */
+ if (slot.sta && !slot.sta->sta.epp_peer &&
+ !test_sta_flag(slot.sta, WLAN_STA_ASSOC))
+ return -EINVAL;
+
key = ieee80211_key_alloc(params->cipher, key_idx, params->key_len,
params->key, params->seq_len, params->seq);
if (IS_ERR(key))
@@ -666,42 +682,26 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
if (pairwise) {
key->conf.flags |= IEEE80211_KEY_FLAG_PAIRWISE;
key->conf.link_id = -1;
- } else {
- key->conf.link_id = link->link_id;
+ } else if (slot.link_sta) {
+ key->conf.link_id = slot.link_sta->link_id;
if (cigtk)
key->conf.flags |= IEEE80211_KEY_FLAG_CIP;
+ } else if (slot.link) {
+ key->conf.link_id = slot.link->link_id;
+
+ if (cigtk)
+ key->conf.flags |= IEEE80211_KEY_FLAG_CIP;
+ } else {
+ /* not reached - slot.link is set even for WEP/WPA-NONE */
+ WARN_ON(1);
+ ieee80211_key_free_unused(key);
+ return -EINVAL;
}
if (params->mode == NL80211_KEY_NO_TX)
key->conf.flags |= IEEE80211_KEY_FLAG_NO_AUTO_TX;
- if (mac_addr) {
- sta = sta_info_get_bss(sdata, mac_addr);
- /*
- * The ASSOC test makes sure the driver is ready to
- * receive the key. When wpa_supplicant has roamed
- * using FT, it attempts to set the key before
- * association has completed, this rejects that attempt
- * so it will set the key again after association.
- *
- * With (re)association frame encryption enabled, cfg80211
- * may deliver keys to mac80211 before the station has
- * associated. In that case, accept the key if the station
- * is an Enhanced Privacy Protection (EPP) peer.
- * 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.
- */
- if (!sta || (!sta->sta.epp_peer &&
- !test_sta_flag(sta, WLAN_STA_ASSOC))) {
- ieee80211_key_free_unused(key);
- return -ENOENT;
- }
- }
-
switch (sdata->vif.type) {
case NL80211_IFTYPE_STATION:
/* mostly handled by cfg80211, but make sure */
@@ -717,7 +717,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
case NL80211_IFTYPE_NAN:
case NL80211_IFTYPE_NAN_DATA:
/* Keys without a station are used for TX only */
- if (sta && test_sta_flag(sta, WLAN_STA_MFP))
+ if (slot.sta && test_sta_flag(slot.sta, WLAN_STA_MFP))
key->conf.flags |= IEEE80211_KEY_FLAG_RX_MGMT;
break;
case NL80211_IFTYPE_ADHOC:
@@ -740,10 +740,11 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
case NL80211_IFTYPE_OCB:
/* shouldn't happen */
WARN_ON_ONCE(1);
- break;
+ ieee80211_key_free_unused(key);
+ return -EINVAL;
}
- err = ieee80211_key_link(key, link, sta);
+ err = ieee80211_key_link(sdata, &slot, key);
/* KRACK protection, shouldn't happen but just silently accept key */
if (err == -EALREADY)
err = 0;
@@ -751,88 +752,28 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
return err;
}
-static struct ieee80211_key *
-ieee80211_lookup_key(struct ieee80211_sub_if_data *sdata, int link_id,
- u8 key_idx, enum nl80211_key_type type,
- const u8 *mac_addr)
-{
- struct ieee80211_local *local __maybe_unused = sdata->local;
- struct ieee80211_link_data *link = &sdata->deflink;
- bool pairwise = type == NL80211_KEYTYPE_PAIRWISE;
- bool cigtk = type == NL80211_KEYTYPE_CIGTK;
- struct ieee80211_key *key;
-
- if (link_id >= 0) {
- link = sdata_dereference(sdata->link[link_id], sdata);
- if (!link)
- return NULL;
- }
-
- if (mac_addr) {
- struct sta_info *sta;
- struct link_sta_info *link_sta;
-
- sta = sta_info_get_bss(sdata, mac_addr);
- if (!sta)
- return NULL;
-
- if (link_id >= 0) {
- link_sta = rcu_dereference_check(sta->link[link_id],
- lockdep_is_held(&local->hw.wiphy->mtx));
- if (!link_sta)
- return NULL;
- } else {
- link_sta = &sta->deflink;
- }
-
- if (pairwise && key_idx < NUM_DEFAULT_KEYS)
- return wiphy_dereference(local->hw.wiphy,
- sta->ptk[key_idx]);
-
- if (cigtk && key_idx < NUM_CTRL_KEYS)
- return wiphy_dereference(local->hw.wiphy,
- link_sta->rx_cigtk[key_idx]);
-
- if (!pairwise && !cigtk &&
- key_idx < NUM_DEFAULT_KEYS +
- NUM_DEFAULT_MGMT_KEYS +
- NUM_DEFAULT_BEACON_KEYS)
- return wiphy_dereference(local->hw.wiphy,
- link_sta->rx_gtk[key_idx]);
-
- return NULL;
- }
-
- if (cigtk)
- return wiphy_dereference(local->hw.wiphy,
- link->tx_cigtk[key_idx]);
-
- key = wiphy_dereference(local->hw.wiphy, link->tx_gtk[key_idx]);
- if (key)
- return key;
-
- /* or maybe it was a WEP key */
- if (key_idx < NUM_DEFAULT_KEYS)
- return wiphy_dereference(local->hw.wiphy, sdata->keys[key_idx]);
-
- return NULL;
-}
-
static int ieee80211_del_key(struct wiphy *wiphy, struct wireless_dev *wdev,
int link_id, u8 key_idx,
enum nl80211_key_type type, const u8 *mac_addr)
{
struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
struct ieee80211_local *local = sdata->local;
- struct ieee80211_key *key;
+ struct ieee80211_key_slot slot;
+ int err;
lockdep_assert_wiphy(local->hw.wiphy);
- key = ieee80211_lookup_key(sdata, link_id, key_idx, type, mac_addr);
- if (!key)
+ err = ieee80211_key_slot_lookup(sdata, link_id, key_idx, 0,
+ type, mac_addr, &slot);
+ if (err)
+ return err;
+ if (WARN_ON(!slot.key))
return -ENOENT;
- ieee80211_key_free(key, sdata->vif.type == NL80211_IFTYPE_STATION);
+ if (!rcu_access_pointer(*slot.key))
+ return -ENOENT;
+
+ ieee80211_key_free(sdata, &slot, sdata->vif.type == NL80211_IFTYPE_STATION);
return 0;
}
@@ -844,25 +785,27 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct wireless_dev *wdev,
void (*callback)(void *cookie,
struct key_params *params))
{
- struct ieee80211_sub_if_data *sdata;
- u8 seq[6] = {0};
- struct key_params params;
+ struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+ struct ieee80211_key_seq kseq = {};
+ struct ieee80211_key_slot slot;
+ struct key_params params = {};
struct ieee80211_key *key;
+ u8 seq[6] = {0};
u64 pn64;
u32 iv32;
u16 iv16;
- int err = -ENOENT;
- struct ieee80211_key_seq kseq = {};
+ int err;
- sdata = IEEE80211_WDEV_TO_SUB_IF(wdev);
+ err = ieee80211_key_slot_lookup(sdata, link_id, key_idx, 0,
+ type, mac_addr, &slot);
+ if (err)
+ return err;
+ if (WARN_ON(!slot.key))
+ return -ENOENT;
- rcu_read_lock();
-
- key = ieee80211_lookup_key(sdata, link_id, key_idx, type, mac_addr);
+ key = wiphy_dereference(wiphy, *slot.key);
if (!key)
- goto out;
-
- memset(¶ms, 0, sizeof(params));
+ return -ENOENT;
params.cipher = key->conf.cipher;
@@ -933,11 +876,7 @@ static int ieee80211_get_key(struct wiphy *wiphy, struct wireless_dev *wdev,
}
callback(cookie, ¶ms);
- err = 0;
-
- out:
- rcu_read_unlock();
- return err;
+ return 0;
}
static int ieee80211_config_default_key(struct wiphy *wiphy,
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index 35f44a856bd3..66bc8b1a4cbf 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -468,69 +468,131 @@ void ieee80211_set_default_beacon_key(struct ieee80211_link_data *link,
__ieee80211_set_default_beacon_key(link, idx);
}
+int ieee80211_key_slot_lookup(struct ieee80211_sub_if_data *sdata,
+ int link_id, u8 key_idx, u32 cipher,
+ enum nl80211_key_type type,
+ const u8 *mac_addr,
+ struct ieee80211_key_slot *slot)
+{
+ struct ieee80211_local *local __maybe_unused = sdata->local;
+ bool pairwise = type == NL80211_KEYTYPE_PAIRWISE;
+ bool cigtk = type == NL80211_KEYTYPE_CIGTK;
+ bool is_wep = cipher == WLAN_CIPHER_SUITE_WEP40 ||
+ cipher == WLAN_CIPHER_SUITE_WEP104;
+ int err;
+
+ memset(slot, 0, sizeof(*slot));
+
+ if (mac_addr) {
+ slot->sta = sta_info_get_bss(sdata, mac_addr);
+ if (!slot->sta) {
+ err = -ENOENT;
+ goto fail;
+ }
+
+ if (pairwise && key_idx < NUM_DEFAULT_KEYS) {
+ slot->key = &slot->sta->ptk[key_idx];
+ return 0;
+ }
+
+ if (link_id >= 0) {
+ slot->link_sta = wiphy_dereference(local->hw.wiphy,
+ slot->sta->link[link_id]);
+ if (!slot->link_sta) {
+ err = -ENOENT;
+ goto fail;
+ }
+ } else if (!ieee80211_vif_is_mld(&sdata->vif)) {
+ slot->link_sta = &slot->sta->deflink;
+ } else {
+ err = -EINVAL;
+ goto fail;
+ }
+
+ if (cigtk) {
+ slot->key = &slot->link_sta->rx_cigtk[key_idx];
+ return 0;
+ }
+
+ if (!pairwise &&
+ key_idx < NUM_DEFAULT_KEYS +
+ NUM_DEFAULT_MGMT_KEYS +
+ NUM_DEFAULT_BEACON_KEYS) {
+ slot->key = &slot->link_sta->rx_gtk[key_idx];
+ return 0;
+ }
+
+ err = -EINVAL;
+ goto fail;
+ }
+
+ if (is_wep && key_idx < NUM_DEFAULT_KEYS) {
+ /* WEP keys can also be default/deflink TX keys */
+ slot->link = &sdata->deflink;
+ slot->key = &sdata->keys[key_idx];
+ return 0;
+ }
+
+ if (link_id >= 0) {
+ slot->link = sdata_dereference(sdata->link[link_id], sdata);
+ if (!slot->link) {
+ err = -ENOLINK;
+ goto fail;
+ }
+ } else {
+ slot->link = &sdata->deflink;
+ }
+
+ if (cigtk) {
+ slot->key = &slot->link->tx_cigtk[key_idx];
+ return 0;
+ }
+
+ slot->key = &slot->link->tx_gtk[key_idx];
+ if (cipher != 0)
+ return 0;
+
+ /* handle the special case for key lookup with cipher==0 */
+ if (rcu_access_pointer(*slot->key))
+ return 0;
+ if (key_idx < NUM_DEFAULT_KEYS) {
+ slot->key = &sdata->keys[key_idx];
+ return 0;
+ }
+
+ err = -EINVAL;
+fail:
+ memset(slot, 0, sizeof(*slot));
+ return err;
+}
+
static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_link_data *link,
- struct sta_info *sta,
- struct link_sta_info *link_sta,
- enum ieee80211_key_flags flags,
+ struct ieee80211_key_slot *slot,
struct ieee80211_key *old,
struct ieee80211_key *new)
{
- bool pairwise = flags & IEEE80211_KEY_FLAG_PAIRWISE;
- bool cip = flags & IEEE80211_KEY_FLAG_CIP;
- int link_id;
- int idx;
- int ret = 0;
bool defunikey, defmultikey, defmgmtkey, defbeaconkey;
- bool is_wep;
+ struct ieee80211_local *local = sdata->local;
+ bool pairwise;
+ int ret = 0;
+ u32 flags;
- lockdep_assert_wiphy(sdata->local->hw.wiphy);
+ lockdep_assert_wiphy(local->hw.wiphy);
+
+ /* the old key is passed in for convenience, but validate it */
+ WARN_ON(old != rcu_access_pointer(*slot->key));
/* caller must provide at least one old/new */
if (WARN_ON(!new && !old))
return 0;
- if (new) {
- idx = new->conf.keyidx;
- is_wep = new->conf.cipher == WLAN_CIPHER_SUITE_WEP40 ||
- new->conf.cipher == WLAN_CIPHER_SUITE_WEP104;
- link_id = new->conf.link_id;
- } else {
- idx = old->conf.keyidx;
- is_wep = old->conf.cipher == WLAN_CIPHER_SUITE_WEP40 ||
- old->conf.cipher == WLAN_CIPHER_SUITE_WEP104;
- link_id = old->conf.link_id;
- }
-
- if (WARN(old && old->conf.link_id != link_id,
- "old link ID %d doesn't match new link ID %d\n",
- old->conf.link_id, link_id))
- return -EINVAL;
-
- if (link_id >= 0) {
- if (!link) {
- link = sdata_dereference(sdata->link[link_id], sdata);
- if (!link)
- return -ENOLINK;
- }
-
- if (sta && !link_sta) {
- link_sta = rcu_dereference_protected(sta->link[link_id],
- lockdep_is_held(&sta->local->hw.wiphy->mtx));
- if (!link_sta)
- return -ENOLINK;
- }
- } else {
- link = &sdata->deflink;
- link_sta = sta ? &sta->deflink : NULL;
- }
-
- if ((is_wep || pairwise) && idx >= NUM_DEFAULT_KEYS)
- return -EINVAL;
+ flags = old ? old->conf.flags : new->conf.flags;
+ pairwise = flags & IEEE80211_KEY_FLAG_PAIRWISE;
WARN_ON(new && old && new->conf.keyidx != old->conf.keyidx);
+ WARN_ON(new && old && new->conf.link_id != old->conf.link_id);
- if (new && sta && pairwise) {
+ if (new && slot->sta && pairwise) {
/* Unicast rekey needs special handling. With Extended Key ID
* old is still NULL for the first rekey.
*/
@@ -545,10 +607,10 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
ret = ieee80211_key_enable_hw_accel(new);
}
} else {
- if (!new->local->wowlan) {
+ if (!local->wowlan) {
ret = ieee80211_key_enable_hw_accel(new);
- } else if (link_id < 0 || !sdata->vif.active_links ||
- BIT(link_id) & sdata->vif.active_links) {
+ } else if (new->conf.link_id < 0 || !sdata->vif.active_links ||
+ BIT(new->conf.link_id) & sdata->vif.active_links) {
new->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
if (!(new->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
@@ -566,35 +628,36 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
spin_unlock_bh(&sdata->local->key_lock);
}
- if (sta) {
+ rcu_assign_pointer(*slot->key, new);
+
+ if (slot->sta) {
if (pairwise) {
- rcu_assign_pointer(sta->ptk[idx], new);
if (new &&
!(new->conf.flags & IEEE80211_KEY_FLAG_NO_AUTO_TX))
_ieee80211_set_tx_key(new, true);
- } else if (cip) {
- rcu_assign_pointer(link_sta->rx_cigtk[idx], new);
- } else {
- rcu_assign_pointer(link_sta->rx_gtk[idx], new);
+
+ /*
+ * Only needed for transition from no key -> key.
+ * Still triggers unnecessary when using Extended Key ID
+ * and installing the second key ID the first time.
+ */
+ if (new && !old)
+ ieee80211_check_fast_rx(slot->sta);
}
- /* Only needed for transition from no key -> key.
- * Still triggers unnecessary when using Extended Key ID
- * and installing the second key ID the first time.
- */
- if (new && !old)
- ieee80211_check_fast_rx(sta);
- } else {
+ } else if (slot->link) {
+ struct ieee80211_link_data *link = slot->link;
+
defunikey = old &&
- old == wiphy_dereference(sdata->local->hw.wiphy,
+ old == wiphy_dereference(local->hw.wiphy,
sdata->default_unicast_key);
defmultikey = old &&
- old == wiphy_dereference(sdata->local->hw.wiphy,
+ old == wiphy_dereference(local->hw.wiphy,
link->default_multicast_key);
defmgmtkey = old &&
- old == wiphy_dereference(sdata->local->hw.wiphy,
+ old == wiphy_dereference(local->hw.wiphy,
link->default_mgmt_key);
defbeaconkey = old &&
- old == wiphy_dereference(sdata->local->hw.wiphy,
+ old == wiphy_dereference(local->hw.wiphy,
link->default_beacon_key);
if (defunikey && !new)
@@ -606,13 +669,6 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
if (defbeaconkey && !new)
__ieee80211_set_default_beacon_key(link, -1);
- if (is_wep || pairwise)
- rcu_assign_pointer(sdata->keys[idx], new);
- else if (cip)
- rcu_assign_pointer(link->tx_cigtk[idx], new);
- else
- rcu_assign_pointer(link->tx_gtk[idx], new);
-
if (defunikey && new)
__ieee80211_set_default_key(link, new->conf.keyidx,
true, false);
@@ -893,32 +949,31 @@ static bool ieee80211_key_identical(struct ieee80211_sub_if_data *sdata,
return !crypto_memneq(tk_old, tk_new, new->conf.keylen);
}
-int ieee80211_key_link(struct ieee80211_key *key,
- struct ieee80211_link_data *link,
- struct sta_info *sta)
+int ieee80211_key_link(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_key_slot *slot,
+ struct ieee80211_key *key)
{
- struct ieee80211_sub_if_data *sdata = link->sdata;
- static atomic_t key_color = ATOMIC_INIT(0);
- struct ieee80211_key *old_key = NULL;
- int idx = key->conf.keyidx;
- bool pairwise = key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE;
/*
* We want to delay tailroom updates only for station - in that
* case it helps roaming speed, but in other cases it hurts and
* can cause warnings to appear.
*/
bool delay_tailroom = sdata->vif.type == NL80211_IFTYPE_STATION;
+ bool pairwise = key->conf.flags & IEEE80211_KEY_FLAG_PAIRWISE;
+ static atomic_t key_color = ATOMIC_INIT(0);
+ struct ieee80211_key *old_key = NULL;
+ int idx = key->conf.keyidx;
int ret;
lockdep_assert_wiphy(sdata->local->hw.wiphy);
- if (sta && pairwise) {
+ old_key = wiphy_dereference(sdata->local->hw.wiphy, *slot->key);
+
+ if (pairwise) {
struct ieee80211_key *alt_key;
- old_key = wiphy_dereference(sdata->local->hw.wiphy,
- sta->ptk[idx]);
alt_key = wiphy_dereference(sdata->local->hw.wiphy,
- sta->ptk[idx ^ 1]);
+ slot->sta->ptk[idx ^ 1]);
/*
* The rekey code assumes that the old and new key are using
@@ -935,44 +990,10 @@ int ieee80211_key_link(struct ieee80211_key *key,
}
/* Set CIP flag if enabled for the station */
- if (sta->sta.cip)
+ if (slot->sta->sta.cip)
key->conf.flags |= IEEE80211_KEY_FLAG_CIP;
- } else if (sta) {
- struct link_sta_info *link_sta = &sta->deflink;
- int link_id = key->conf.link_id;
-
- if (link_id >= 0) {
- link_sta = rcu_dereference_protected(sta->link[link_id],
- lockdep_is_held(&sta->local->hw.wiphy->mtx));
- if (!link_sta) {
- ret = -ENOLINK;
- goto out;
- }
- }
-
- if (key->conf.flags & IEEE80211_KEY_FLAG_CIP)
- old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link_sta->rx_cigtk[idx]);
- else
- old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link_sta->rx_gtk[idx]);
} else {
- if (key->conf.flags & IEEE80211_KEY_FLAG_CIP) {
- old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link->tx_cigtk[idx]);
- } else {
- if (idx < NUM_DEFAULT_KEYS)
- old_key = wiphy_dereference(sdata->local->hw.wiphy,
- sdata->keys[idx]);
-
- if (!old_key)
- old_key = wiphy_dereference(sdata->local->hw.wiphy,
- link->tx_gtk[idx]);
- }
- }
-
- /* Non-pairwise keys must also not switch the cipher on rekey */
- if (!pairwise) {
+ /* Non-pairwise keys must also not switch the cipher on rekey */
if (old_key && old_key->conf.cipher != key->conf.cipher) {
ret = -EOPNOTSUPP;
goto out;
@@ -990,7 +1011,7 @@ int ieee80211_key_link(struct ieee80211_key *key,
key->local = sdata->local;
key->sdata = sdata;
- key->sta = sta;
+ key->sta = slot->sta;
/*
* Assign a unique ID to every key so we can easily prevent mixed
@@ -999,7 +1020,7 @@ int ieee80211_key_link(struct ieee80211_key *key,
key->color = atomic_inc_return(&key_color);
/* keep this flag for easier access later */
- if (pairwise && sta && sta->sta.spp_amsdu)
+ if (pairwise && slot->sta->sta.spp_amsdu)
key->conf.flags |= IEEE80211_KEY_FLAG_SPP_AMSDU;
/* A CIP related key must be GCMP-256 (really GMAC-256) */
@@ -1011,34 +1032,36 @@ int ieee80211_key_link(struct ieee80211_key *key,
increment_tailroom_need_count(sdata);
- ret = ieee80211_key_replace(sdata, link, sta, NULL,
- key->conf.flags, old_key, key);
-
- if (!ret) {
- ieee80211_debugfs_key_add(key);
- ieee80211_key_destroy(old_key, delay_tailroom);
- } else {
- ieee80211_key_free(key, delay_tailroom);
+ ret = ieee80211_key_replace(sdata, slot, old_key, key);
+ if (ret) {
+ decrease_tailroom_need_count(sdata, 1);
+ key->local = NULL;
+ key->sdata = NULL;
+ goto out;
}
- key = NULL;
+ ieee80211_debugfs_key_add(key);
+ ieee80211_key_destroy(old_key, delay_tailroom);
+ return 0;
out:
ieee80211_key_free_unused(key);
return ret;
}
-void ieee80211_key_free(struct ieee80211_key *key, bool delay_tailroom)
+void ieee80211_key_free(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_key_slot *slot,
+ bool delay_tailroom)
{
+ struct ieee80211_key *key;
+
+ key = sdata_dereference(*slot->key, sdata);
if (!key)
return;
- /*
- * Replace key with nothingness if it was ever used.
- */
+ /* replace key with nothing if it was ever used */
if (key->sdata)
- ieee80211_key_replace(key->sdata, NULL, key->sta, NULL,
- key->conf.flags, key, NULL);
+ ieee80211_key_replace(key->sdata, slot, key, NULL);
ieee80211_key_destroy(key, delay_tailroom);
}
@@ -1171,8 +1194,6 @@ EXPORT_SYMBOL(ieee80211_iter_keys_atomic);
static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata,
struct list_head *keys)
{
- struct ieee80211_key *key, *tmp;
-
decrease_tailroom_need_count(sdata,
sdata->crypto_tx_tailroom_pending_dec);
sdata->crypto_tx_tailroom_pending_dec = 0;
@@ -1180,9 +1201,19 @@ static void ieee80211_free_keys_iface(struct ieee80211_sub_if_data *sdata,
ieee80211_debugfs_key_remove_mgmt_default(sdata);
ieee80211_debugfs_key_remove_beacon_default(sdata);
- list_for_each_entry_safe(key, tmp, &sdata->key_list, list) {
- ieee80211_key_replace(key->sdata, NULL, key->sta, NULL,
- key->conf.flags, key, NULL);
+ for (int i = 0; i < ARRAY_SIZE(sdata->keys); i++) {
+ struct ieee80211_key_slot slot = {
+ /* WEP keys can also be default/deflink TX keys */
+ .link = &sdata->deflink,
+ .key = &sdata->keys[i],
+ };
+ struct ieee80211_key *key;
+
+ key = sdata_dereference(*slot.key, sdata);
+ if (!key)
+ continue;
+
+ ieee80211_key_replace(sdata, &slot, key, NULL);
list_add_tail(&key->list, keys);
}
@@ -1194,15 +1225,33 @@ void ieee80211_remove_link_keys(struct ieee80211_link_data *link,
{
struct ieee80211_sub_if_data *sdata = link->sdata;
struct ieee80211_local *local = sdata->local;
- struct ieee80211_key *key, *tmp;
+ struct ieee80211_key_slot slot = {
+ .link = link,
+ };
lockdep_assert_wiphy(local->hw.wiphy);
- list_for_each_entry_safe(key, tmp, &sdata->key_list, list) {
- if (key->conf.link_id != link->link_id)
+ for (int i = 0; i < ARRAY_SIZE(link->tx_gtk); i++) {
+ struct ieee80211_key *key;
+
+ slot.key = &link->tx_gtk[i];
+ key = sdata_dereference(*slot.key, sdata);
+ if (!key)
continue;
- ieee80211_key_replace(key->sdata, link, key->sta, NULL,
- key->conf.flags, key, NULL);
+
+ ieee80211_key_replace(sdata, &slot, key, NULL);
+ list_add_tail(&key->list, keys);
+ }
+
+ for (int i = 0; i < ARRAY_SIZE(link->tx_cigtk); i++) {
+ struct ieee80211_key *key;
+
+ slot.key = &link->tx_cigtk[i];
+ key = sdata_dereference(*slot.key, sdata);
+ if (!key)
+ continue;
+
+ ieee80211_key_replace(sdata, &slot, key, NULL);
list_add_tail(&key->list, keys);
}
}
@@ -1232,13 +1281,18 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata,
lockdep_assert_wiphy(local->hw.wiphy);
+ ieee80211_remove_link_keys(&sdata->deflink, &keys);
ieee80211_free_keys_iface(sdata, &keys);
if (sdata->vif.type == NL80211_IFTYPE_AP) {
- list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
+ list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list) {
+ ieee80211_remove_link_keys(&vlan->deflink, &keys);
ieee80211_free_keys_iface(vlan, &keys);
+ }
}
+ WARN_ON(!list_empty(&sdata->key_list));
+
if (!list_empty(&keys) || force_synchronize)
synchronize_net();
list_for_each_entry_safe(key, tmp, &keys, list)
@@ -1268,17 +1322,22 @@ void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata,
void ieee80211_unlink_link_sta_keys(struct ieee80211_local *local,
struct link_sta_info *link_sta)
{
+ struct ieee80211_key_slot slot = {
+ .sta = link_sta->sta,
+ .link_sta = link_sta,
+ };
+
lockdep_assert_wiphy(local->hw.wiphy);
for (int i = 0; i < ARRAY_SIZE(link_sta->rx_gtk); i++) {
struct ieee80211_key *key;
int ret;
- key = wiphy_dereference(local->hw.wiphy, link_sta->rx_gtk[i]);
+ slot.key = &link_sta->rx_gtk[i];
+ key = wiphy_dereference(local->hw.wiphy, *slot.key);
if (!key)
continue;
- ret = ieee80211_key_replace(key->sdata, NULL, key->sta, link_sta,
- key->conf.flags, key, NULL);
+ ret = ieee80211_key_replace(key->sdata, &slot, key, NULL);
if (WARN(ret, "failed to remove STA link key (%d)\n", ret))
continue;
list_add(&key->list, &link_sta->key_destroy_list);
@@ -1290,11 +1349,11 @@ void ieee80211_unlink_link_sta_keys(struct ieee80211_local *local,
struct ieee80211_key *key;
int ret;
- key = wiphy_dereference(local->hw.wiphy, link_sta->rx_cigtk[i]);
+ slot.key = &link_sta->rx_cigtk[i];
+ key = wiphy_dereference(local->hw.wiphy, *slot.key);
if (!key)
continue;
- ret = ieee80211_key_replace(key->sdata, NULL, key->sta, link_sta,
- key->conf.flags, key, NULL);
+ ret = ieee80211_key_replace(key->sdata, &slot, key, NULL);
if (WARN(ret, "failed to remove STA link key (%d)\n", ret))
continue;
list_add(&key->list, &link_sta->key_destroy_list);
@@ -1316,17 +1375,20 @@ void ieee80211_free_link_sta_keys(struct link_sta_info *link_sta)
void ieee80211_free_sta_keys(struct ieee80211_local *local,
struct sta_info *sta)
{
- struct ieee80211_key *key;
- int i;
+ struct ieee80211_key_slot slot = {
+ .sta = sta,
+ };
lockdep_assert_wiphy(local->hw.wiphy);
- for (i = 0; i < NUM_DEFAULT_KEYS; i++) {
- key = wiphy_dereference(local->hw.wiphy, sta->ptk[i]);
+ for (int i = 0; i < ARRAY_SIZE(sta->ptk); i++) {
+ struct ieee80211_key *key;
+
+ slot.key = &sta->ptk[i];
+ key = wiphy_dereference(local->hw.wiphy, *slot.key);
if (!key)
continue;
- ieee80211_key_replace(key->sdata, NULL, key->sta, NULL,
- key->conf.flags, key, NULL);
+ ieee80211_key_replace(key->sdata, &slot, key, NULL);
__ieee80211_key_destroy(key, key->sdata->vif.type ==
NL80211_IFTYPE_STATION);
}
@@ -1488,6 +1550,7 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
{
struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
struct ieee80211_local *local = sdata->local;
+ struct ieee80211_key_slot slot = {};
struct link_sta_info *link_sta;
struct ieee80211_key *prev_key;
struct ieee80211_key *key;
@@ -1526,12 +1589,15 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
return ERR_PTR(-ENOLINK);
}
+ slot.sta = sta;
+ slot.link_sta = link_sta;
+
if (cigtk)
- prev_key = wiphy_dereference(local->hw.wiphy,
- link_sta->rx_cigtk[idx]);
+ slot.key = &link_sta->rx_cigtk[idx];
else
- prev_key = wiphy_dereference(local->hw.wiphy,
- link_sta->rx_gtk[idx]);
+ slot.key = &link_sta->rx_gtk[idx];
+
+ prev_key = wiphy_dereference(local->hw.wiphy, *slot.key);
if (!prev_key) {
if (cigtk) {
@@ -1573,7 +1639,7 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
key->conf.link_id = link_data->link_id;
- err = ieee80211_key_link(key, link_data, sta);
+ err = ieee80211_key_link(sdata, &slot, key);
if (err)
return ERR_PTR(err);
diff --git a/net/mac80211/key.h b/net/mac80211/key.h
index db86bdc5744f..2fc985d41e8f 100644
--- a/net/mac80211/key.h
+++ b/net/mac80211/key.h
@@ -141,6 +141,43 @@ struct ieee80211_key {
/* from sta_info.h, but cannot include that */
struct link_sta_info;
+struct ieee80211_key_slot {
+ struct ieee80211_key __rcu **key;
+
+ struct sta_info *sta;
+ struct link_sta_info *link_sta;
+
+ struct ieee80211_link_data *link;
+};
+
+/**
+ * ieee80211_key_slot_lookup - look up a key installation slot
+ * @sdata: the interface
+ * @link_id: The link ID, or -1.
+ * @key_idx: The key ID.
+ * @cipher: The cipher suite, may be 0 if not known, which implies
+ * the lookup is for getting key information or deleting a key,
+ * which in turn implies the function should return a used slot.
+ * Note this is all only relevant for distinguishing AP-side
+ * group keys (TX only) and WEP/WPA-NONE keys.
+ * @type: the key type (from nl80211)
+ * @mac_addr: the station's MAC address, if any
+ * @slot: the output data filled by the function
+ *
+ * Return: an error code, or zero on success, in which case
+ * the slot information is filled:
+ * - the key pointer is always given
+ * - the sta for keys related to a station
+ * (pairwise, RX group keys)
+ * - the link_sta for RX group keys
+ * - the link for TX group keys (and WEP)
+ */
+int ieee80211_key_slot_lookup(struct ieee80211_sub_if_data *sdata,
+ int link_id, u8 key_idx, u32 cipher,
+ enum nl80211_key_type type,
+ const u8 *mac_addr,
+ struct ieee80211_key_slot *slot);
+
struct ieee80211_key *
ieee80211_key_alloc(u32 cipher, int idx, size_t key_len,
const u8 *key_data,
@@ -149,11 +186,13 @@ ieee80211_key_alloc(u32 cipher, int idx, size_t key_len,
* Insert a key into data structures (sdata, sta if necessary)
* to make it used, free old key. On failure, also free the new key.
*/
-int ieee80211_key_link(struct ieee80211_key *key,
- struct ieee80211_link_data *link,
- struct sta_info *sta);
+int ieee80211_key_link(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_key_slot *slot,
+ struct ieee80211_key *key);
int ieee80211_set_tx_key(struct ieee80211_key *key);
-void ieee80211_key_free(struct ieee80211_key *key, bool delay_tailroom);
+void ieee80211_key_free(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_key_slot *slot,
+ bool delay_tailroom);
void ieee80211_key_free_unused(struct ieee80211_key *key);
void ieee80211_set_default_key(struct ieee80211_link_data *link, int idx,
bool uni, bool multi);
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 09/11] wifi: mac80211: refactor tailroom needed checks
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (7 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 08/11] wifi: mac80211: unify key finding/installation Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation Johannes Berg
2026-10-13 12:59 ` [PATCH 11/11] wifi: mac80211: optimize RCU on group key rekeying Johannes Berg
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
Whether a key needs TX tailroom reserved for it is open-coded in
several places as a test of the relevant flags. Create a small
helper function and use it instead.
The check for KEY_FLAG_UPLOADED_TO_HARDWARE is new in a few of
the places, but it's set appropriately there.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/key.c | 27 ++++++++++++++-------------
1 file changed, 14 insertions(+), 13 deletions(-)
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index 66bc8b1a4cbf..d32c61a90d93 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -118,6 +118,16 @@ static void decrease_tailroom_need_count(struct ieee80211_sub_if_data *sdata,
sdata->crypto_tx_tailroom_needed_cnt -= delta;
}
+static bool ieee80211_key_needs_tailroom(struct ieee80211_key *key)
+{
+ if (key->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
+ IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
+ IEEE80211_KEY_FLAG_RESERVE_TAILROOM))
+ return true;
+
+ return !(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE);
+}
+
static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
{
struct ieee80211_sub_if_data *sdata = key->sdata;
@@ -135,10 +145,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
* so clear that flag now to avoid trying to remove
* it again later.
*/
- if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE &&
- !(key->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
- IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
- IEEE80211_KEY_FLAG_RESERVE_TAILROOM)))
+ if (!ieee80211_key_needs_tailroom(key))
increment_tailroom_need_count(sdata);
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
@@ -204,9 +211,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
if (!ret) {
key->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
- if (!(key->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
- IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
- IEEE80211_KEY_FLAG_RESERVE_TAILROOM)))
+ if (!ieee80211_key_needs_tailroom(key))
decrease_tailroom_need_count(sdata, 1);
WARN_ON((key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE) &&
@@ -276,9 +281,7 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
!(sdata->vif.active_links & BIT(key->conf.link_id)))
return;
- if (!(key->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
- IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
- IEEE80211_KEY_FLAG_RESERVE_TAILROOM)))
+ if (!ieee80211_key_needs_tailroom(key))
increment_tailroom_need_count(sdata);
pubsta = sta ? &sta->sta : NULL;
@@ -612,9 +615,7 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
} else if (new->conf.link_id < 0 || !sdata->vif.active_links ||
BIT(new->conf.link_id) & sdata->vif.active_links) {
new->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
- if (!(new->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
- IEEE80211_KEY_FLAG_PUT_MIC_SPACE |
- IEEE80211_KEY_FLAG_RESERVE_TAILROOM)))
+ if (!ieee80211_key_needs_tailroom(new))
decrease_tailroom_need_count(sdata, 1);
}
}
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (8 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 09/11] wifi: mac80211: refactor tailroom needed checks Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
2026-10-13 18:22 ` Johannes Berg
2026-10-13 12:59 ` [PATCH 11/11] wifi: mac80211: optimize RCU on group key rekeying Johannes Berg
10 siblings, 1 reply; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
On a new key installation, going from not having a key to
now having one, we currently synchronize_net() because the
key is first assumed to be handled in software (requiring
tailroom allocation, and that requires not transmitting a
packet without enough tailroom, hence synchronization). It
is then offloaded, usually not requiring tailroom anymore.
During a new key installation, however, it's actually not
the case that this is needed, since the key only becomes
reachable to the TX path _after_ being installed into the
hardware.
Restructure the code so that the increment/decrement of
the tailroom counter is skipped for newly installed keys.
In the case of rekeying, this doesn't work since the old
key is removed from hardware and may still be reachable
by software, so the tailroom counter still increments to
one until the old key is destroyed. This could be avoided
with more effort, requiring tracking more precisely if a
key is even used for TX at any given time.
Assisted-by: LLM
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/key.c | 38 ++++++++++++++++++++++++++------------
1 file changed, 26 insertions(+), 12 deletions(-)
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index d32c61a90d93..caaea117d4e0 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -128,7 +128,8 @@ static bool ieee80211_key_needs_tailroom(struct ieee80211_key *key)
return !(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE);
}
-static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
+static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key,
+ bool update_tailroom)
{
struct ieee80211_sub_if_data *sdata = key->sdata;
struct sta_info *sta;
@@ -145,7 +146,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
* so clear that flag now to avoid trying to remove
* it again later.
*/
- if (!ieee80211_key_needs_tailroom(key))
+ if (update_tailroom && !ieee80211_key_needs_tailroom(key))
increment_tailroom_need_count(sdata);
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
@@ -211,7 +212,7 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
if (!ret) {
key->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
- if (!ieee80211_key_needs_tailroom(key))
+ if (update_tailroom && !ieee80211_key_needs_tailroom(key))
decrease_tailroom_need_count(sdata, 1);
WARN_ON((key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE) &&
@@ -607,22 +608,38 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
ieee80211_key_disable_hw_accel(old);
if (new)
- ret = ieee80211_key_enable_hw_accel(new);
+ ret = ieee80211_key_enable_hw_accel(new, false);
}
} else {
if (!local->wowlan) {
- ret = ieee80211_key_enable_hw_accel(new);
+ ret = ieee80211_key_enable_hw_accel(new, false);
} else if (new->conf.link_id < 0 || !sdata->vif.active_links ||
BIT(new->conf.link_id) & sdata->vif.active_links) {
new->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
- if (!ieee80211_key_needs_tailroom(new))
- decrease_tailroom_need_count(sdata, 1);
}
}
if (ret)
return ret;
+ /*
+ * The new key only becomes reachable by the TX path - and can thus
+ * require software crypto tailroom - once it is assigned to the slot
+ * (possibly not even then, depending on the key and default key, but
+ * that's too complex to handle here).
+ *
+ * This is after it was installed in the device, so that the device
+ * has it when it's used.
+ *
+ * This means that we can do the tailroom-needed accounting here and
+ * only ever call increment_tailroom_need_count() if needed now, in
+ * case it's fully offloaded it's not actually needed.
+ *
+ * This avoids the RCU synchronize on first key installation.
+ */
+ if (new && ieee80211_key_needs_tailroom(new))
+ increment_tailroom_need_count(sdata);
+
if (new) {
spin_lock_bh(&sdata->local->key_lock);
list_add_tail(&new->list, &sdata->key_list);
@@ -1031,11 +1048,8 @@ int ieee80211_key_link(struct ieee80211_sub_if_data *sdata,
goto out;
}
- increment_tailroom_need_count(sdata);
-
ret = ieee80211_key_replace(sdata, slot, old_key, key);
if (ret) {
- decrease_tailroom_need_count(sdata, 1);
key->local = NULL;
key->sdata = NULL;
goto out;
@@ -1087,7 +1101,7 @@ void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata)
list_for_each_entry(key, &sdata->key_list, list) {
if (!(key->flags & KEY_FLAG_TAINTED))
increment_tailroom_need_count(sdata);
- ieee80211_key_enable_hw_accel(key);
+ ieee80211_key_enable_hw_accel(key, true);
}
}
}
@@ -1717,7 +1731,7 @@ int ieee80211_key_switch_links(struct ieee80211_sub_if_data *sdata,
!(add_links_mask & BIT(key->conf.link_id)))
continue;
- ret = ieee80211_key_enable_hw_accel(key);
+ ret = ieee80211_key_enable_hw_accel(key, true);
if (ret)
return ret;
}
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* [PATCH 11/11] wifi: mac80211: optimize RCU on group key rekeying
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
` (9 preceding siblings ...)
2026-10-13 12:59 ` [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation Johannes Berg
@ 2026-10-13 12:59 ` Johannes Berg
10 siblings, 0 replies; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 12:59 UTC (permalink / raw)
To: linux-wireless; +Cc: Johannes Berg
From: Johannes Berg <johannes.berg@intel.com>
After the previous change, RCU synchronization is avoided
on the first key install if possible (if it goes to HW).
On the client side, group keys are used only for RX and
therefore don't affect tailroom requirements at all, but
accounting is currently still done for them.
With the previous cleanups and the slot mechanism, it's
now relatively simple to differentiate RX-only keys. Add
a flag to the slot and key (where it tracks for later HW
enablement/disablement etc.) and skip the whole tailroom
accounting for RX-only keys.
This way, no RCU synchronisation is needed on group key
rekeying on the client side.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
---
net/mac80211/key.c | 47 +++++++++++++++++++++++++++++++++-------------
net/mac80211/key.h | 6 ++++++
2 files changed, 40 insertions(+), 13 deletions(-)
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index caaea117d4e0..a18760cd8209 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -118,6 +118,13 @@ static void decrease_tailroom_need_count(struct ieee80211_sub_if_data *sdata,
sdata->crypto_tx_tailroom_needed_cnt -= delta;
}
+/* RX-only keys don't need tailroom since that's for TX only */
+static bool ieee80211_key_relevant_for_tailroom(struct ieee80211_key *key)
+{
+ return !(key->flags & KEY_FLAG_RX_ONLY);
+}
+
+/* check if this individual key needs tailroom right now */
static bool ieee80211_key_needs_tailroom(struct ieee80211_key *key)
{
if (key->conf.flags & (IEEE80211_KEY_FLAG_GENERATE_MMIC |
@@ -146,7 +153,9 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key,
* so clear that flag now to avoid trying to remove
* it again later.
*/
- if (update_tailroom && !ieee80211_key_needs_tailroom(key))
+ if (update_tailroom &&
+ ieee80211_key_relevant_for_tailroom(key) &&
+ !ieee80211_key_needs_tailroom(key))
increment_tailroom_need_count(sdata);
key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
@@ -212,7 +221,9 @@ static int ieee80211_key_enable_hw_accel(struct ieee80211_key *key,
if (!ret) {
key->flags |= KEY_FLAG_UPLOADED_TO_HARDWARE;
- if (update_tailroom && !ieee80211_key_needs_tailroom(key))
+ if (update_tailroom &&
+ ieee80211_key_relevant_for_tailroom(key) &&
+ !ieee80211_key_needs_tailroom(key))
decrease_tailroom_need_count(sdata, 1);
WARN_ON((key->conf.flags & IEEE80211_KEY_FLAG_PUT_IV_SPACE) &&
@@ -282,7 +293,8 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
!(sdata->vif.active_links & BIT(key->conf.link_id)))
return;
- if (!ieee80211_key_needs_tailroom(key))
+ if (ieee80211_key_relevant_for_tailroom(key) &&
+ !ieee80211_key_needs_tailroom(key))
increment_tailroom_need_count(sdata);
pubsta = sta ? &sta->sta : NULL;
@@ -515,6 +527,7 @@ int ieee80211_key_slot_lookup(struct ieee80211_sub_if_data *sdata,
if (cigtk) {
slot->key = &slot->link_sta->rx_cigtk[key_idx];
+ slot->rx_only = true;
return 0;
}
@@ -523,6 +536,7 @@ int ieee80211_key_slot_lookup(struct ieee80211_sub_if_data *sdata,
NUM_DEFAULT_MGMT_KEYS +
NUM_DEFAULT_BEACON_KEYS) {
slot->key = &slot->link_sta->rx_gtk[key_idx];
+ slot->rx_only = true;
return 0;
}
@@ -637,7 +651,8 @@ static int ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
*
* This avoids the RCU synchronize on first key installation.
*/
- if (new && ieee80211_key_needs_tailroom(new))
+ if (new && ieee80211_key_relevant_for_tailroom(new) &&
+ ieee80211_key_needs_tailroom(new))
increment_tailroom_need_count(sdata);
if (new) {
@@ -890,14 +905,16 @@ static void __ieee80211_key_cleanup(struct ieee80211_key *key,
ieee80211_debugfs_key_remove(key);
- if (delay_tailroom) {
- /* see ieee80211_delayed_tailroom_dec */
- sdata->crypto_tx_tailroom_pending_dec++;
- wiphy_delayed_work_queue(sdata->local->hw.wiphy,
- &sdata->dec_tailroom_needed_wk,
- HZ / 2);
- } else {
- decrease_tailroom_need_count(sdata, 1);
+ if (ieee80211_key_relevant_for_tailroom(key)) {
+ if (delay_tailroom) {
+ /* see ieee80211_delayed_tailroom_dec */
+ sdata->crypto_tx_tailroom_pending_dec++;
+ wiphy_delayed_work_queue(sdata->local->hw.wiphy,
+ &sdata->dec_tailroom_needed_wk,
+ HZ / 2);
+ } else {
+ decrease_tailroom_need_count(sdata, 1);
+ }
}
}
}
@@ -1030,6 +1047,8 @@ int ieee80211_key_link(struct ieee80211_sub_if_data *sdata,
key->local = sdata->local;
key->sdata = sdata;
key->sta = slot->sta;
+ if (slot->rx_only)
+ key->flags |= KEY_FLAG_RX_ONLY;
/*
* Assign a unique ID to every key so we can easily prevent mixed
@@ -1099,7 +1118,8 @@ void ieee80211_reenable_keys(struct ieee80211_sub_if_data *sdata)
if (ieee80211_sdata_running(sdata)) {
list_for_each_entry(key, &sdata->key_list, list) {
- if (!(key->flags & KEY_FLAG_TAINTED))
+ if (ieee80211_key_relevant_for_tailroom(key) &&
+ !(key->flags & KEY_FLAG_TAINTED))
increment_tailroom_need_count(sdata);
ieee80211_key_enable_hw_accel(key, true);
}
@@ -1606,6 +1626,7 @@ ieee80211_gtk_rekey_add(struct ieee80211_vif *vif,
slot.sta = sta;
slot.link_sta = link_sta;
+ slot.rx_only = true;
if (cigtk)
slot.key = &link_sta->rx_cigtk[idx];
diff --git a/net/mac80211/key.h b/net/mac80211/key.h
index 2fc985d41e8f..89afe23276c7 100644
--- a/net/mac80211/key.h
+++ b/net/mac80211/key.h
@@ -33,10 +33,14 @@ 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_RX_ONLY: Key is installed into an RX-only key slot, so it's
+ * never used by the TX path and therefore never affects tailroom
+ * allocation requirements.
*/
enum ieee80211_internal_key_flags {
KEY_FLAG_UPLOADED_TO_HARDWARE = BIT(0),
KEY_FLAG_TAINTED = BIT(1),
+ KEY_FLAG_RX_ONLY = BIT(2),
};
enum ieee80211_internal_tkip_state {
@@ -148,6 +152,8 @@ struct ieee80211_key_slot {
struct link_sta_info *link_sta;
struct ieee80211_link_data *link;
+
+ bool rx_only;
};
/**
--
2.56.0
^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation
2026-10-13 12:59 ` [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation Johannes Berg
@ 2026-10-13 18:22 ` Johannes Berg
2026-10-14 10:27 ` Luka Gejak
0 siblings, 1 reply; 14+ messages in thread
From: Johannes Berg @ 2026-10-13 18:22 UTC (permalink / raw)
To: linux-wireless
On Tue, 2026-10-13 at 14:59 +0200, Johannes Berg wrote:
> From: Johannes Berg <johannes.berg@intel.com>
>
> On a new key installation, going from not having a key to
> now having one, we currently synchronize_net() because the
> key is first assumed to be handled in software (requiring
> tailroom allocation, and that requires not transmitting a
> packet without enough tailroom, hence synchronization). It
> is then offloaded, usually not requiring tailroom anymore.
>
> During a new key installation, however, it's actually not
> the case that this is needed, since the key only becomes
> reachable to the TX path _after_ being installed into the
> hardware.
>
> Restructure the code so that the increment/decrement of
> the tailroom counter is skipped for newly installed keys.
>
> In the case of rekeying, this doesn't work since the old
> key is removed from hardware and may still be reachable
> by software, so the tailroom counter still increments to
> one until the old key is destroyed. This could be avoided
> with more effort, requiring tracking more precisely if a
> key is even used for TX at any given time.
This is well and all, but I'm starting to think we should just not do
this. Given that SW crypto really should be the slow-path, I think we
can instead simply remove the RCU sync *completely*, and accept that in
the race case, the (SW) crypto path may have to expand tailroom (and
might drop a frame if allocation fails).
That's far simpler, no need to add the RX_ONLY flag I did in patch 11
etc., ultimately instead of adding code it ends up removing some, since
we can also get rid of the dec_tailroom_needed_wk and all that.
Thoughts?
johannes
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation
2026-10-13 18:22 ` Johannes Berg
@ 2026-10-14 10:27 ` Luka Gejak
0 siblings, 0 replies; 14+ messages in thread
From: Luka Gejak @ 2026-10-14 10:27 UTC (permalink / raw)
To: Johannes Berg, linux-wireless
Hi Johannes,
On Tue Oct 13, 2026 at 6:22 PM UTC, Johannes Berg wrote:
> This is well and all, but I'm starting to think we should just not do
> this. Given that SW crypto really should be the slow-path, I think we
> can instead simply remove the RCU sync *completely*, and accept that in
> the race case, the (SW) crypto path may have to expand tailroom (and
> might drop a frame if allocation fails).
>
> That's far simpler, no need to add the RX_ONLY flag I did in patch 11
> etc., ultimately instead of adding code it ends up removing some, since
> we can also get rid of the dec_tailroom_needed_wk and all that.
>
> Thoughts?
Agreed, the counter only exists to avoid synchronize_net(). Two things I
would want in the same series though, because "expand, or maybe drop a
frame" is not quite what the code does today.
The data crypto paths warn and drop rather than expand. tkip, ccmp and
gcmp all WARN_ON(skb_tailroom(skb) < tail) and return -1, and
ieee80211_tx_h_michael_mic_add() returns TX_DROP. So either those grow a
pskb_expand_head fallback, or the WARN_ONs become plain checks.
And the counter is also the cloned skb unshare trigger in
ieee80211_skb_resize(). Without it a header released clone with slack at
the tail skips the unshare, and software crypto then writes the MIC into
memory it does not own alone, which skb_tailroom() would not catch. An
unshare in the encrypt path would.
With that the counter is only an optimisation, so no objection from me.
Best regards,
Luka Gejak
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-10-14 10:27 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13 12:59 wifi: cfg80211/mac80211: key handling improvements Johannes Berg
2026-10-13 12:59 ` [PATCH 01/11] wifi: cfg80211: reject WEP/TKIP with multi-link Johannes Berg
2026-10-13 12:59 ` [PATCH 02/11] wifi: mac80211: replace ieee80211_iter_keys_rcu() Johannes Berg
2026-10-13 12:59 ` [PATCH 03/11] wifi: cfg80211: support C/B/I/GTK per AP in client modes Johannes Berg
2026-10-13 12:59 ` [PATCH 04/11] wifi: mac80211: clarify per-STA GTK handling code/docs Johannes Berg
2026-10-13 12:59 ` [PATCH 05/11] wifi: mac80211: use per-STA GTK in client mode Johannes Berg
2026-10-13 12:59 ` [PATCH 06/11] wifi: mac80211: allow drivers to install AP GTK on STA Johannes Berg
2026-10-13 12:59 ` [PATCH 07/11] wifi: mac80211: remove lookup_key() dead code Johannes Berg
2026-10-13 12:59 ` [PATCH 08/11] wifi: mac80211: unify key finding/installation Johannes Berg
2026-10-13 12:59 ` [PATCH 09/11] wifi: mac80211: refactor tailroom needed checks Johannes Berg
2026-10-13 12:59 ` [PATCH 10/11] wifi: mac80211: avoid tailroom accounting on new key installation Johannes Berg
2026-10-13 18:22 ` Johannes Berg
2026-10-14 10:27 ` Luka Gejak
2026-10-13 12:59 ` [PATCH 11/11] wifi: mac80211: optimize RCU on group key rekeying Johannes Berg
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.