diff --git a/net/wireless/core.c b/net/wireless/core.c index cb3039fd8dbf..01cba0d10b92 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -39,7 +39,8 @@ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("wireless configuration support"); MODULE_ALIAS_GENL_FAMILY(NL80211_GENL_NAME); -/* RCU-protected (and RTNL for writers) */ +/* RCU-protected / RTNL / rdev_mtx */ +DEFINE_MUTEX(cfg80211_rdev_mtx); LIST_HEAD(cfg80211_rdev_list); int cfg80211_rdev_list_generation; @@ -58,7 +59,8 @@ struct cfg80211_registered_device *cfg80211_rdev_by_wiphy_idx(int wiphy_idx) { struct cfg80211_registered_device *result = NULL, *rdev; - ASSERT_RTNL(); + lockdep_assert(lockdep_rtnl_is_held() || + lockdep_is_held(&cfg80211_rdev_mtx)); list_for_each_entry(rdev, &cfg80211_rdev_list, list) { if (rdev->wiphy_idx == wiphy_idx) { @@ -124,7 +126,7 @@ static int cfg80211_dev_check_name(struct cfg80211_registered_device *rdev, } int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, - char *newname) + const char *newname) { int result; @@ -932,8 +934,10 @@ int wiphy_register(struct wiphy *wiphy) return res; } + mutex_lock(&cfg80211_rdev_mtx); list_add_rcu(&rdev->list, &cfg80211_rdev_list); cfg80211_rdev_list_generation++; + mutex_unlock(&cfg80211_rdev_mtx); /* add to debugfs */ rdev->wiphy.debugfsdir = debugfs_create_dir(wiphy_name(&rdev->wiphy), @@ -1027,6 +1031,7 @@ void wiphy_unregister(struct wiphy *wiphy) rfkill_unregister(rdev->wiphy.rfkill); rtnl_lock(); + mutex_lock(&cfg80211_rdev_mtx); wiphy_lock(&rdev->wiphy); nl80211_notify_wiphy(rdev, NL80211_CMD_DEL_WIPHY); rdev->wiphy.registered = false; @@ -1051,6 +1056,7 @@ void wiphy_unregister(struct wiphy *wiphy) device_del(&rdev->wiphy.dev); wiphy_unlock(&rdev->wiphy); + mutex_unlock(&cfg80211_rdev_mtx); rtnl_unlock(); flush_work(&rdev->scan_done_wk); diff --git a/net/wireless/core.h b/net/wireless/core.h index 503b1b1feb79..8e510b0966a0 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -152,6 +152,7 @@ static inline u64 cfg80211_assign_cookie(struct cfg80211_registered_device *rdev } extern struct workqueue_struct *cfg80211_wq; +extern struct mutex cfg80211_rdev_mtx; extern struct list_head cfg80211_rdev_list; extern int cfg80211_rdev_list_generation; @@ -306,7 +307,7 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev); void cfg80211_dev_free(struct cfg80211_registered_device *rdev); int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, - char *newname); + const char *newname); void ieee80211_set_bitrate_flags(struct wiphy *wiphy); diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 39295af93799..dcef1d3ab599 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -143,7 +143,8 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs) struct cfg80211_registered_device *rdev = NULL, *tmp; struct net_device *netdev; - ASSERT_RTNL(); + lockdep_assert(lockdep_rtnl_is_held() || + lockdep_is_held(&cfg80211_rdev_mtx)); if (!attrs[NL80211_ATTR_WIPHY] && !attrs[NL80211_ATTR_IFINDEX] && @@ -161,13 +162,15 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs) tmp = cfg80211_rdev_by_wiphy_idx(wdev_id >> 32); if (tmp) { - /* make sure wdev exists */ - list_for_each_entry(wdev, &tmp->wiphy.wdev_list, list) { + /* make sure wdev exists - at least for now */ + rcu_read_lock(); + list_for_each_entry_rcu(wdev, &tmp->wiphy.wdev_list, list) { if (wdev->identifier != (u32)wdev_id) continue; found = true; break; } + rcu_read_unlock(); if (!found) tmp = NULL; @@ -181,13 +184,15 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs) if (attrs[NL80211_ATTR_IFINDEX]) { int ifindex = nla_get_u32(attrs[NL80211_ATTR_IFINDEX]); - netdev = __dev_get_by_index(netns, ifindex); + rcu_read_lock(); + netdev = dev_get_by_index_rcu(netns, ifindex); if (netdev) { if (netdev->ieee80211_ptr) tmp = wiphy_to_rdev( netdev->ieee80211_ptr->wiphy); else tmp = NULL; + rcu_read_unlock(); /* not wireless device -- return error */ if (!tmp) @@ -198,6 +203,8 @@ __cfg80211_rdev_from_attrs(struct net *netns, struct nlattr **attrs) return ERR_PTR(-EINVAL); rdev = tmp; + } else { + rcu_read_unlock(); } } @@ -996,17 +1003,23 @@ static int nl80211_prepare_wdev_dump(struct netlink_callback *cb, return err; } - rtnl_lock(); - *wdev = __cfg80211_wdev_from_attrs(NULL, sock_net(cb->skb->sk), + mutex_lock(&cfg80211_rdev_mtx); + *rdev = __cfg80211_rdev_from_attrs(sock_net(cb->skb->sk), + attrbuf); + if (IS_ERR(*rdev)) { + mutex_unlock(&cfg80211_rdev_mtx); + return PTR_ERR(*rdev); + } + mutex_lock(&(*rdev)->wiphy.mtx); + mutex_unlock(&cfg80211_rdev_mtx); + *wdev = __cfg80211_wdev_from_attrs(*rdev, sock_net(cb->skb->sk), attrbuf); kfree(attrbuf_free); if (IS_ERR(*wdev)) { - rtnl_unlock(); + mutex_unlock(&(*rdev)->wiphy.mtx); return PTR_ERR(*wdev); } *rdev = wiphy_to_rdev((*wdev)->wiphy); - mutex_lock(&(*rdev)->wiphy.mtx); - rtnl_unlock(); /* 0 is the first index - add 1 to parse only once */ cb->args[0] = (*rdev)->wiphy_idx + 1; cb->args[1] = (*wdev)->identifier; @@ -3307,11 +3320,21 @@ static int nl80211_set_channel(struct sk_buff *skb, struct genl_info *info) return __nl80211_set_channel(rdev, netdev, info); } +static int nl80211_handle_wiphy_rename(struct genl_info *info) +{ + struct cfg80211_registered_device *rdev; + const char *name = nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME]); + + rdev = __cfg80211_rdev_from_attrs(genl_info_net(info), info->attrs); + + return cfg80211_dev_rename(rdev, name); +} + static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = NULL; + struct wireless_dev *wdev = NULL; struct net_device *netdev = NULL; - struct wireless_dev *wdev; int result = 0, rem_txq_params = 0; struct nlattr *nl_txq_params; u32 changed; @@ -3320,7 +3343,15 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) u8 coverage_class = 0; u32 txq_limit = 0, txq_memory_limit = 0, txq_quantum = 0; - rtnl_lock(); + /* this requires RTNL, handle it separately, it's rare anyway */ + if (info->attrs[NL80211_ATTR_WIPHY_NAME]) { + rtnl_lock(); + result = nl80211_handle_wiphy_rename(info); + rtnl_unlock(); + if (result) + return result; + } + /* * Try to find the wiphy and netdev. Normally this * function shouldn't need the netdev, but this is @@ -3330,44 +3361,30 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) * also passed a netdev to set_wiphy, so that it is * possible to let that go to the right netdev! */ - - 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) - rdev = wiphy_to_rdev(netdev->ieee80211_ptr->wiphy); - else - netdev = NULL; + mutex_lock(&cfg80211_rdev_mtx); + rdev = __cfg80211_rdev_from_attrs(genl_info_net(info), info->attrs); + if (IS_ERR(rdev)) { + mutex_unlock(&cfg80211_rdev_mtx); + return PTR_ERR(rdev); } + wiphy_lock(&rdev->wiphy); + mutex_unlock(&cfg80211_rdev_mtx); - if (!netdev) { - rdev = __cfg80211_rdev_from_attrs(genl_info_net(info), + if (info->attrs[NL80211_ATTR_IFINDEX]) { + wdev = __cfg80211_wdev_from_attrs(rdev, genl_info_net(info), info->attrs); - if (IS_ERR(rdev)) { - rtnl_unlock(); - return PTR_ERR(rdev); + if (IS_ERR(wdev)) { + result = PTR_ERR(wdev); + goto out; + } + if (!wdev->netdev) { + result = -ENODEV; + goto out; } - wdev = NULL; - netdev = NULL; - result = 0; - } else - wdev = netdev->ieee80211_ptr; - - wiphy_lock(&rdev->wiphy); - - /* - * end workaround code, by now the rdev is available - * and locked, and wdev may or may not be NULL. - */ - - if (info->attrs[NL80211_ATTR_WIPHY_NAME]) - result = cfg80211_dev_rename( - rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME])); - rtnl_unlock(); - if (result) - goto out; + /* note that this cannot go away under wiphy_lock() */ + netdev = wdev->netdev; + } if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) { struct ieee80211_txq_params txq_params; @@ -10933,7 +10950,7 @@ static int nl80211_testmode_do(struct sk_buff *skb, struct genl_info *info) struct wireless_dev *wdev; int err; - lockdep_assert_held(&rdev->wiphy.mtx); + lockdep_assert_wiphy(&rdev->wiphy); wdev = __cfg80211_wdev_from_attrs(rdev, genl_info_net(info), info->attrs); @@ -15340,36 +15357,53 @@ static int nl80211_pre_doit(const struct genl_ops *ops, struct sk_buff *skb, struct cfg80211_registered_device *rdev = NULL; struct wireless_dev *wdev; struct net_device *dev; + int err; #ifdef CPTCFG_REJECT_NONUPSTREAM_NL80211 if (info->genlhdr->cmd >= __NL80211_CMD_NONUPSTREAM_START) return -EOPNOTSUPP; #endif - rtnl_lock(); - if (ops->internal_flags & NL80211_FLAG_NEED_WIPHY) { + if (ops->internal_flags & NL80211_FLAG_NEED_RTNL) + rtnl_lock(); + + /* + * wdev references must be under wiphy mutex, netdevs + * have their own dev_hold() so they're fine + */ + WARN_ON(ops->internal_flags & NL80211_FLAG_NO_WIPHY_MTX && + ops->internal_flags & NL80211_FLAG_NEED_WDEV); + + mutex_lock(&cfg80211_rdev_mtx); + if (ops->internal_flags & (NL80211_FLAG_NEED_WIPHY | + NL80211_FLAG_NEED_NETDEV | + NL80211_FLAG_NEED_WDEV)) { rdev = cfg80211_get_dev_from_info(genl_info_net(info), info); if (IS_ERR(rdev)) { - rtnl_unlock(); - return PTR_ERR(rdev); + err = PTR_ERR(rdev); + goto unlock; } info->user_ptr[0] = rdev; - } else if (ops->internal_flags & NL80211_FLAG_NEED_NETDEV || - ops->internal_flags & NL80211_FLAG_NEED_WDEV) { - wdev = __cfg80211_wdev_from_attrs(NULL, genl_info_net(info), + wiphy_lock(&rdev->wiphy); + /* we keep the mutex locked until post_doit */ + __release(&rdev->wiphy.mtx); + } + + if (ops->internal_flags & NL80211_FLAG_NEED_NETDEV || + ops->internal_flags & NL80211_FLAG_NEED_WDEV) { + wdev = __cfg80211_wdev_from_attrs(rdev, genl_info_net(info), info->attrs); if (IS_ERR(wdev)) { - rtnl_unlock(); - return PTR_ERR(wdev); + err = PTR_ERR(wdev); + goto unlock; } dev = wdev->netdev; - rdev = wiphy_to_rdev(wdev->wiphy); if (ops->internal_flags & NL80211_FLAG_NEED_NETDEV) { if (!dev) { - rtnl_unlock(); - return -EINVAL; + err = -EINVAL; + goto unlock; } info->user_ptr[1] = dev; @@ -15379,23 +15413,22 @@ static int nl80211_pre_doit(const struct genl_ops *ops, struct sk_buff *skb, if (ops->internal_flags & NL80211_FLAG_CHECK_NETDEV_UP && !wdev_running(wdev)) { - rtnl_unlock(); - return -ENETDOWN; + err = -ENETDOWN; + goto unlock; } dev_hold(dev); - info->user_ptr[0] = rdev; } - if (rdev && !(ops->internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) { - wiphy_lock(&rdev->wiphy); - /* we keep the mutex locked until post_doit */ - __release(&rdev->wiphy.mtx); + if (rdev && (ops->internal_flags & NL80211_FLAG_NO_WIPHY_MTX)) { + __acquire(&rdev->wiphy.mtx); + wiphy_unlock(&rdev->wiphy); } - if (!(ops->internal_flags & NL80211_FLAG_NEED_RTNL)) - rtnl_unlock(); - return 0; + err = 0; +unlock: + mutex_unlock(&cfg80211_rdev_mtx); + return err; } static void nl80211_post_doit(const struct genl_ops *ops, struct sk_buff *skb,