[PATCH wireless-next 1/2] wifi: nxpwifi: protect sta_list against concurrent add and delete
Linmao Li <[email protected]>
| Newsgroups | org.kernel.vger.linux-wireless,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
nxpwifi_del_sta_entry() unlinks an entry from priv->sta_list without
holding priv->sta_list_spinlock. The comment above it gives the
reason: the function is only reached from cfg80211_ops.del_station,
which runs under the wiphy mutex.
That mutex serialises this callback against other users of the same
lock, including wiphy_work items. It covers neither of the two other
writers of the same list:
nxpwifi_add_sta_entry() from nxpwifi_uap_event_sta_assoc()
nxpwifi_del_all_sta_list() from nxpwifi_uap_event_bss_idle()
Both run from nxpwifi_process_event() on the driver's main work queue,
a plain workqueue that never takes the wiphy lock, and both take
sta_list_spinlock around their update. A station associating, or the
BSS going idle, can therefore modify priv->sta_list while a hostapd
del_station is walking it and unlinking an entry.
Take the lock here too. As the traversal then runs under the write
lock, list_for_each_entry() replaces the _rcu variant, matching
nxpwifi_del_all_sta_list().
The same call path reaches nxpwifi_get_sta_entry() without
rcu_read_lock(). Entries are freed with kfree_rcu() and every other
caller holds it, so take it around the lookup as well.
Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x")
Signed-off-by: Linmao Li <[email protected]>
---
drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 2 ++
drivers/net/wireless/nxp/nxpwifi/util.c | 8 ++++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c
index 5cc8cdf594d3e..939177bcc6cde 100644
--- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c
+++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c
@@ -1539,9 +1539,11 @@ nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev,
eth_zero_addr(deauth_mac);
+ rcu_read_lock();
sta_node = nxpwifi_get_sta_entry(priv, params->mac);
if (sta_node)
ether_addr_copy(deauth_mac, params->mac);
+ rcu_read_unlock();
if (is_valid_ether_addr(deauth_mac)) {
ret = nxpwifi_uap_sta_deauth(priv, deauth_mac);
diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c
index bbfefb81d8d3b..ae8444a141058 100644
--- a/drivers/net/wireless/nxp/nxpwifi/util.c
+++ b/drivers/net/wireless/nxp/nxpwifi/util.c
@@ -1041,19 +1041,23 @@ nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies,
}
}
-/* Delete a station from list; called under cfg80211 mutex. */
+/* Delete a station from list. */
void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac)
{
struct nxpwifi_sta_node *node;
- list_for_each_entry_rcu(node, &priv->sta_list, list) {
+ spin_lock_bh(&priv->sta_list_spinlock);
+
+ list_for_each_entry(node, &priv->sta_list, list) {
if (!memcmp(node->mac_addr, mac, ETH_ALEN)) {
list_del_rcu(&node->list);
kfree_rcu(node, rcu);
break;
}
}
+
+ spin_unlock_bh(&priv->sta_list_spinlock);
}
/* Delete all stations from list. */
--
2.25.1