]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: cfg80211: convert pmsr_free_wk to wiphy_work to fix deadlock
authorPeddolla Harshavardhan Reddy <peddolla.reddy@oss.qualcomm.com>
Fri, 3 Jul 2026 08:25:23 +0000 (13:55 +0530)
committerJohannes Berg <johannes.berg@intel.com>
Mon, 6 Jul 2026 12:11:08 +0000 (14:11 +0200)
When a netlink socket that owns a PMSR session is closed,
cfg80211_release_pmsr() clears the request's nl_portid and queues
pmsr_free_wk to call cfg80211_pmsr_process_abort() asynchronously.

If the interface tears down concurrently, cfg80211_pmsr_wdev_down()
is called under wiphy_lock and calls cancel_work_sync(&pmsr_free_wk)
to wait for any running work. The work function acquires wiphy_lock
via guard(wiphy) before calling process_abort.

This is a deadlock: wdev_down holds wiphy_lock and blocks inside
cancel_work_sync(); pmsr_free_wk blocks trying to acquire that same
wiphy_lock. Neither thread can proceed.

The same deadlock is reachable from cfg80211_leave_locked(), which
calls cfg80211_pmsr_wdev_down() for all interface types under
wiphy_lock.

Fix this by converting pmsr_free_wk from a plain work_struct to a
wiphy_work. The wiphy_work dispatcher holds wiphy_lock when running
work items, so the explicit guard(wiphy) in the work function is no
longer needed. wiphy_work_cancel() can be called safely while holding
wiphy_lock - since wiphy_lock prevents the work from running
concurrently, wiphy_work_cancel() never blocks, eliminating the
deadlock.

Remove the cancel_work_sync() for pmsr_free_wk from the
NETDEV_GOING_DOWN handler. cfg80211_leave(), called unconditionally
just before it, already cancels any pending work under wiphy_lock
via wiphy_work_cancel() inside cfg80211_pmsr_wdev_down().

Fixes: 6dccbc9f3e1d ("wifi: cfg80211: cancel pmsr_free_wk in cfg80211_pmsr_wdev_down")
Signed-off-by: Peddolla Harshavardhan Reddy <peddolla.reddy@oss.qualcomm.com>
Link: https://patch.msgid.link/20260703082523.2629324-1-peddolla.reddy@oss.qualcomm.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
net/wireless/core.c
net/wireless/core.h
net/wireless/pmsr.c

index 8188ad200de5393ed324d725016e3fef7160cd26..3751a1d74765ab98cb9244ab61b414873334f708 100644 (file)
@@ -7265,7 +7265,7 @@ struct wireless_dev {
 
        struct list_head pmsr_list;
        spinlock_t pmsr_lock;
-       struct work_struct pmsr_free_wk;
+       struct wiphy_work pmsr_free_wk;
 
        unsigned long unprot_beacon_reported;
 
index 2c729a7aca12dd797d1b45e604b8ee8934895669..082f0ee12f1b7b949b618baa3f95ff218baba0ce 100644 (file)
@@ -1614,7 +1614,7 @@ void cfg80211_init_wdev(struct wireless_dev *wdev)
        INIT_LIST_HEAD(&wdev->mgmt_registrations);
        INIT_LIST_HEAD(&wdev->pmsr_list);
        spin_lock_init(&wdev->pmsr_lock);
-       INIT_WORK(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk);
+       wiphy_work_init(&wdev->pmsr_free_wk, cfg80211_pmsr_free_wk);
 
 #ifdef CONFIG_CFG80211_WEXT
        wdev->wext.default_key = -1;
@@ -1748,7 +1748,6 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
                        cfg80211_remove_links(wdev);
                /* since we just did cfg80211_leave() nothing to do there */
                cancel_work_sync(&wdev->disconnect_wk);
-               cancel_work_sync(&wdev->pmsr_free_wk);
                break;
        case NETDEV_DOWN:
                wiphy_lock(&rdev->wiphy);
index df47ed6208a50de3c680c0302591b8b752fcbcc4..f60c66b88677255b52b8d76383241f4a888a9e71 100644 (file)
@@ -586,7 +586,7 @@ cfg80211_get_6ghz_power_type(const u8 *elems, size_t elems_len,
 
 void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid);
 void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev);
-void cfg80211_pmsr_free_wk(struct work_struct *work);
+void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work);
 
 void cfg80211_remove_link(struct wireless_dev *wdev, unsigned int link_id);
 void cfg80211_remove_links(struct wireless_dev *wdev);
index c8447448f3a505d202d7109bc10fdf79ce936efb..2c8db33d9c30981a0d0795ed25c3ddbeea9cb445 100644 (file)
@@ -807,13 +807,11 @@ static void cfg80211_pmsr_process_abort(struct wireless_dev *wdev)
        }
 }
 
-void cfg80211_pmsr_free_wk(struct work_struct *work)
+void cfg80211_pmsr_free_wk(struct wiphy *wiphy, struct wiphy_work *work)
 {
        struct wireless_dev *wdev = container_of(work, struct wireless_dev,
                                                 pmsr_free_wk);
 
-       guard(wiphy)(wdev->wiphy);
-
        cfg80211_pmsr_process_abort(wdev);
 }
 
@@ -829,7 +827,7 @@ void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev)
        }
        spin_unlock_bh(&wdev->pmsr_lock);
 
-       cancel_work_sync(&wdev->pmsr_free_wk);
+       wiphy_work_cancel(wdev->wiphy, &wdev->pmsr_free_wk);
        if (found)
                cfg80211_pmsr_process_abort(wdev);
 
@@ -844,7 +842,7 @@ void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid)
        list_for_each_entry(req, &wdev->pmsr_list, list) {
                if (req->nl_portid == portid) {
                        req->nl_portid = 0;
-                       schedule_work(&wdev->pmsr_free_wk);
+                       wiphy_work_queue(wdev->wiphy, &wdev->pmsr_free_wk);
                }
        }
        spin_unlock_bh(&wdev->pmsr_lock);