From: Johannes Berg The RTNL is only used for wiphy names to serialize checking for name collisions and setting the name, across all wiphys. Since the wiphy list can be walked under the cfg80211_mutex, use that instead. In nl80211_set_wiphy() this requires taking it before the wiphy mutex, and then the RTNL is no longer needed there at all if the netdev is looked up under RCU. Since the netns of the wiphy and its netdevs is now also stable under the cfg80211_mutex, only the netdev unregistering needs to be handled, by looking up the wdev again under the wiphy mutex. Assisted-by: LLM Signed-off-by: Johannes Berg --- net/wireless/core.c | 10 ++++----- net/wireless/nl80211.c | 47 ++++++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/net/wireless/core.c b/net/wireless/core.c index d648df6b8037..c9ad4f72e70d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -94,7 +94,7 @@ static int cfg80211_dev_check_name(struct cfg80211_registered_device *rdev, struct cfg80211_registered_device *rdev2; int wiphy_idx, taken = -1, digits; - ASSERT_RTNL(); + lockdep_assert_held(&cfg80211_mutex); if (strlen(newname) > NL80211_WIPHY_NAME_MAXLEN) return -EINVAL; @@ -127,7 +127,7 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, { int result; - ASSERT_RTNL(); + lockdep_assert_held(&cfg80211_mutex); lockdep_assert_wiphy(&rdev->wiphy); /* Ignore nop renames */ @@ -664,16 +664,16 @@ struct wiphy *wiphy_new_nm(const struct cfg80211_ops *ops, int sizeof_priv, if (requested_name && requested_name[0]) { int rv; - rtnl_lock(); + mutex_lock(&cfg80211_mutex); rv = cfg80211_dev_check_name(rdev, requested_name); if (rv < 0) { - rtnl_unlock(); + mutex_unlock(&cfg80211_mutex); goto use_default_name; } rv = dev_set_name(&rdev->wiphy.dev, "%s", requested_name); - rtnl_unlock(); + mutex_unlock(&cfg80211_mutex); if (rv) goto use_default_name; } else { diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index bf64cafa02fc..48262db9d5e3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -4299,17 +4299,18 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = NULL; struct net_device *netdev = NULL; - struct wireless_dev *wdev; + struct wireless_dev *wdev = NULL; int result = 0, rem_txq_params = 0; struct nlattr *nl_txq_params; - u32 changed; + u32 changed, wdev_id = 0; u8 retry_short = 0, retry_long = 0; u32 frag_threshold = 0, rts_threshold = 0; u8 coverage_class = 0; u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0; int radio_idx = -1; - rtnl_lock(); + /* keeps the wiphy registered and its netns stable */ + mutex_lock(&cfg80211_mutex); /* * Try to find the wiphy and netdev. Normally this * function shouldn't need the netdev, but this is @@ -4323,28 +4324,44 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) if (info->attrs[NL80211_ATTR_IFINDEX]) { int ifindex = nla_get_u32(info->attrs[NL80211_ATTR_IFINDEX]); - netdev = __dev_get_by_index(genl_info_net(info), ifindex); - if (netdev && netdev->ieee80211_ptr) + rcu_read_lock(); + netdev = dev_get_by_index_rcu(genl_info_net(info), ifindex); + if (netdev && netdev->ieee80211_ptr) { rdev = wiphy_to_rdev(netdev->ieee80211_ptr->wiphy); - else - netdev = NULL; + wdev_id = netdev->ieee80211_ptr->identifier; + } + rcu_read_unlock(); } - if (!netdev) { + if (!rdev) { + /* RCU for the wdev list walk, cfg80211_mutex keeps rdev */ + rcu_read_lock(); rdev = __cfg80211_rdev_from_attrs(genl_info_net(info), info->attrs); + rcu_read_unlock(); if (IS_ERR(rdev)) { - rtnl_unlock(); + mutex_unlock(&cfg80211_mutex); return PTR_ERR(rdev); } - wdev = NULL; - netdev = NULL; - result = 0; - } else - wdev = netdev->ieee80211_ptr; + } guard(wiphy)(&rdev->wiphy); + /* the netdev may have been unregistered after the lookup */ + if (wdev_id) { + list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { + if (wdev->identifier == wdev_id) + break; + } + if (list_entry_is_head(wdev, &rdev->wiphy.wdev_list, list)) { + mutex_unlock(&cfg80211_mutex); + return -ENODEV; + } + netdev = wdev->netdev; + } else { + netdev = NULL; + } + /* * end workaround code, by now the rdev is available * and locked, and wdev may or may not be NULL. @@ -4353,7 +4370,7 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) if (info->attrs[NL80211_ATTR_WIPHY_NAME]) result = cfg80211_dev_rename( rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME])); - rtnl_unlock(); + mutex_unlock(&cfg80211_mutex); if (result) return result; -- 2.56.0