Skip to content

Commit ea6b209

Browse files
jmberg-inteltorvalds
authored andcommitted
cfg80211: fix locking in netlink owner interface destruction
Harald Arnesen reported [1] a deadlock at reboot time, and after he captured a stack trace a picture developed of what's going on: The distribution he's using is using iwd (not wpa_supplicant) to manage wireless. iwd will usually use the "socket owner" option when it creates new interfaces, so that they're automatically destroyed when it quits (unexpectedly or otherwise). This is also done by wpa_supplicant, but it doesn't do it for the normal one, only for additional ones, which is different with iwd. Anyway, during shutdown, iwd quits while the netdev is still UP, i.e. IFF_UP is set. This causes the stack trace that Linus so nicely transcribed from the pictures: cfg80211_destroy_iface_wk() takes wiphy_lock -> cfg80211_destroy_ifaces() ->ieee80211_del_iface ->ieeee80211_if_remove ->cfg80211_unregister_wdev ->unregister_netdevice_queue ->dev_close_many ->__dev_close_many ->raw_notifier_call_chain ->cfg80211_netdev_notifier_call and that last call tries to take wiphy_lock again. In commit a05829a ("cfg80211: avoid holding the RTNL when calling the driver") I had taken into account the possibility of recursing from cfg80211 into cfg80211_netdev_notifier_call() via the network stack, but only for NETDEV_UNREGISTER, not for what happens here, NETDEV_GOING_DOWN and NETDEV_DOWN notifications. Additionally, while this worked still back in commit 78f22b6 ("cfg80211: allow userspace to take ownership of interfaces"), it missed another corner case: unregistering a netdev will cause dev_close() to be called, and thus stop wireless operations (e.g. disconnecting), but there are some types of virtual interfaces in wifi that don't have a netdev - for that we need an additional call to cfg80211_leave(). So, to fix this mess, change cfg80211_destroy_ifaces() to not require the wiphy_lock(), but instead make it acquire it, but only after it has actually closed all the netdevs on the list, and then call cfg80211_leave() as well before removing them from the driver, to fix the second issue. The locking change in this requires modifying the nl80211 call to not get the wiphy lock passed in, but acquire it by itself after flushing any potentially pending destruction requests. [1] https://lore.kernel.org/r/09464e67-f3de-ac09-28a3-e27b7914ee7d@skogtun.org Cc: stable@vger.kernel.org # 5.12 Reported-by: Harald Arnesen <harald@skogtun.org> Fixes: 776a39b ("cfg80211: call cfg80211_destroy_ifaces() with wiphy lock held") Fixes: 78f22b6 ("cfg80211: allow userspace to take ownership of interfaces") Signed-off-by: Johannes Berg <johannes.berg@intel.com> Tested-by: Harald Arnesen <harald@skogtun.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 4a0225c commit ea6b209

2 files changed

Lines changed: 36 additions & 9 deletions

File tree

net/wireless/core.c

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,29 @@ static void cfg80211_event_work(struct work_struct *work)
332332
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
333333
{
334334
struct wireless_dev *wdev, *tmp;
335+
bool found = false;
335336

336337
ASSERT_RTNL();
337-
lockdep_assert_wiphy(&rdev->wiphy);
338338

339+
list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
340+
if (wdev->nl_owner_dead) {
341+
if (wdev->netdev)
342+
dev_close(wdev->netdev);
343+
found = true;
344+
}
345+
}
346+
347+
if (!found)
348+
return;
349+
350+
wiphy_lock(&rdev->wiphy);
339351
list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) {
340-
if (wdev->nl_owner_dead)
352+
if (wdev->nl_owner_dead) {
353+
cfg80211_leave(rdev, wdev);
341354
rdev_del_virtual_intf(rdev, wdev);
355+
}
342356
}
357+
wiphy_unlock(&rdev->wiphy);
343358
}
344359

345360
static void cfg80211_destroy_iface_wk(struct work_struct *work)
@@ -350,9 +365,7 @@ static void cfg80211_destroy_iface_wk(struct work_struct *work)
350365
destroy_work);
351366

352367
rtnl_lock();
353-
wiphy_lock(&rdev->wiphy);
354368
cfg80211_destroy_ifaces(rdev);
355-
wiphy_unlock(&rdev->wiphy);
356369
rtnl_unlock();
357370
}
358371

net/wireless/nl80211.c

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3929,7 +3929,7 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
39293929
return err;
39303930
}
39313931

3932-
static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
3932+
static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
39333933
{
39343934
struct cfg80211_registered_device *rdev = info->user_ptr[0];
39353935
struct vif_params params;
@@ -3938,9 +3938,6 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
39383938
int err;
39393939
enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
39403940

3941-
/* to avoid failing a new interface creation due to pending removal */
3942-
cfg80211_destroy_ifaces(rdev);
3943-
39443941
memset(&params, 0, sizeof(params));
39453942

39463943
if (!info->attrs[NL80211_ATTR_IFNAME])
@@ -4028,6 +4025,21 @@ static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
40284025
return genlmsg_reply(msg, info);
40294026
}
40304027

4028+
static int nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
4029+
{
4030+
struct cfg80211_registered_device *rdev = info->user_ptr[0];
4031+
int ret;
4032+
4033+
/* to avoid failing a new interface creation due to pending removal */
4034+
cfg80211_destroy_ifaces(rdev);
4035+
4036+
wiphy_lock(&rdev->wiphy);
4037+
ret = _nl80211_new_interface(skb, info);
4038+
wiphy_unlock(&rdev->wiphy);
4039+
4040+
return ret;
4041+
}
4042+
40314043
static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
40324044
{
40334045
struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -15040,7 +15052,9 @@ static const struct genl_small_ops nl80211_small_ops[] = {
1504015052
.doit = nl80211_new_interface,
1504115053
.flags = GENL_UNS_ADMIN_PERM,
1504215054
.internal_flags = NL80211_FLAG_NEED_WIPHY |
15043-
NL80211_FLAG_NEED_RTNL,
15055+
NL80211_FLAG_NEED_RTNL |
15056+
/* we take the wiphy mutex later ourselves */
15057+
NL80211_FLAG_NO_WIPHY_MTX,
1504415058
},
1504515059
{
1504615060
.cmd = NL80211_CMD_DEL_INTERFACE,

0 commit comments

Comments
 (0)