]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: cfg80211: use wiphy work for socket owner autodisconnect
authorCen Zhang <zzzccc427@gmail.com>
Mon, 6 Jul 2026 15:24:18 +0000 (23:24 +0800)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 7 Jul 2026 07:27:42 +0000 (09:27 +0200)
nl80211_netlink_notify() walks the cfg80211 wireless device list when a
NETLINK_GENERIC socket is released. If the socket owns a connection, the
notifier queues the embedded wdev->disconnect_wk work item.

That work is a plain work_struct today. NETDEV_GOING_DOWN cancels it, but a
NETLINK_URELEASE notifier that already observed conn_owner_nlportid can
queue it after that cancel returns. _cfg80211_unregister_wdev() then
removes the wdev from the list and waits for RCU readers, but
synchronize_net() does not drain work queued by such a reader.

Make the autodisconnect work a wiphy_work instead. The callback already
needs the wiphy mutex, and wiphy_work runs under that mutex. This lets
teardown cancel pending autodisconnect work while holding the mutex,
without a cancel_work_sync() vs. worker locking concern.

Also cancel the wiphy work after list_del_rcu() and synchronize_net(). Any
NETLINK_URELEASE notifier that had already reached the wdev list has then
either queued the work and it is removed, or can no longer find the wdev.

Fixes: bd2522b16884 ("cfg80211: NL80211_ATTR_SOCKET_OWNER support for CMD_CONNECT")
Suggested-by: Johannes Berg <johannes@sipsolutions.net>
Assisted-by: Codex:gpt-5.5
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
Link: https://patch.msgid.link/20260706152418.779226-1-zzzccc427@gmail.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
net/wireless/core.c
net/wireless/core.h
net/wireless/nl80211.c
net/wireless/sme.c

index 3751a1d74765ab98cb9244ab61b414873334f708..f5abf1db7558bcb9d7654e5c60f0bf02ae2380bc 100644 (file)
@@ -7228,7 +7228,7 @@ struct wireless_dev {
        enum ieee80211_bss_type conn_bss_type;
        u32 conn_owner_nlportid;
 
-       struct work_struct disconnect_wk;
+       struct wiphy_work disconnect_wk;
        u8 disconnect_bssid[ETH_ALEN];
 
        struct list_head event_list;
index 082f0ee12f1b7b949b618baa3f95ff218baba0ce..610238d723fff798039e54ad4472c3077cd3596b 100644 (file)
@@ -1425,6 +1425,7 @@ static void _cfg80211_unregister_wdev(struct wireless_dev *wdev,
        list_del_rcu(&wdev->list);
        synchronize_net();
        rdev->devlist_generation++;
+       wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk);
 
        cfg80211_mlme_purge_registrations(wdev);
 
@@ -1638,7 +1639,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev)
             wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
                wdev->netdev->priv_flags |= IFF_DONT_BRIDGE;
 
-       INIT_WORK(&wdev->disconnect_wk, cfg80211_autodisconnect_wk);
+       wiphy_work_init(&wdev->disconnect_wk, cfg80211_autodisconnect_wk);
 }
 
 void cfg80211_register_wdev(struct cfg80211_registered_device *rdev,
@@ -1744,10 +1745,11 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
                break;
        case NETDEV_GOING_DOWN:
                cfg80211_leave(rdev, wdev, -1);
-               scoped_guard(wiphy, &rdev->wiphy)
+               scoped_guard(wiphy, &rdev->wiphy) {
                        cfg80211_remove_links(wdev);
-               /* since we just did cfg80211_leave() nothing to do there */
-               cancel_work_sync(&wdev->disconnect_wk);
+                       /* since we just did cfg80211_leave() nothing to do there */
+                       wiphy_work_cancel(wdev->wiphy, &wdev->disconnect_wk);
+               }
                break;
        case NETDEV_DOWN:
                wiphy_lock(&rdev->wiphy);
index f60c66b88677255b52b8d76383241f4a888a9e71..ac6ce9f967ec70221f15323e24c2640e71213dae 100644 (file)
@@ -428,7 +428,7 @@ void __cfg80211_port_authorized(struct wireless_dev *wdev, const u8 *peer_addr,
                                const u8 *td_bitmap, u8 td_bitmap_len);
 int cfg80211_mgd_wext_connect(struct cfg80211_registered_device *rdev,
                              struct wireless_dev *wdev);
-void cfg80211_autodisconnect_wk(struct work_struct *work);
+void cfg80211_autodisconnect_wk(struct wiphy *wiphy, struct wiphy_work *work);
 
 /* SME implementation */
 void cfg80211_conn_work(struct work_struct *work);
index 625c99cf70a3e281e007f3f97aebf90ef8f80ff9..5adcb6bd0fc56db46d111cdac0ca7ccc57fa2d27 100644 (file)
@@ -22954,7 +22954,8 @@ static int nl80211_netlink_notify(struct notifier_block * nb,
                                wdev->nl_owner_dead = true;
                                schedule_work(&rdev->destroy_work);
                        } else if (wdev->conn_owner_nlportid == notify->portid) {
-                               schedule_work(&wdev->disconnect_wk);
+                               wiphy_work_queue(wdev->wiphy,
+                                                &wdev->disconnect_wk);
                        }
 
                        cfg80211_release_pmsr(wdev, notify->portid);
index b451df3096dd1eebbc8c91a4f170450e0ef1d6ed..2a719b5c487e39fa85da5526e2491ec2039f277b 100644 (file)
@@ -1578,13 +1578,11 @@ int cfg80211_disconnect(struct cfg80211_registered_device *rdev,
  * Used to clean up after the connection / connection attempt owner socket
  * disconnects
  */
-void cfg80211_autodisconnect_wk(struct work_struct *work)
+void cfg80211_autodisconnect_wk(struct wiphy *wiphy, struct wiphy_work *work)
 {
        struct wireless_dev *wdev =
                container_of(work, struct wireless_dev, disconnect_wk);
-       struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
-
-       guard(wiphy)(wdev->wiphy);
+       struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
 
        if (wdev->conn_owner_nlportid) {
                switch (wdev->iftype) {