From 18b54e7928b47945f78a96f8d1cc134869f27445 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 6 Oct 2026 12:04:06 +0200 Subject: [PATCH] wifi: cfg80211: update channel DFS data under wiphy mutex We check the DFS data for a channel under wiphy mutex, but the code sometimes writes it under RTNL only. Fix that to avoid data races and to prepare things for later removal of the RTNL here. cfg80211_dfs_channels_update_work() must change a bit more to do this, since it locks other wiphys and nesting locks is, while possible, too difficult: track the changes in an array and update everything afterwards. Link: https://patch.msgid.link/20261006120655.e90b467f7739.I9074ec4427736f66fd5d9d47a53ccd94a1f260bf@changeid Signed-off-by: Johannes Berg --- net/wireless/mlme.c | 90 +++++++++++++++++++++++++++++++++++++-------- net/wireless/reg.c | 6 ++- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index a0d1cde26f0c..131039b675fe 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -1070,18 +1070,49 @@ void cfg80211_sched_dfs_chan_update(struct cfg80211_registered_device *rdev) queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, 0); } +struct cfg80211_dfs_chan_expiry { + struct ieee80211_channel *chan; + unsigned long entered; + enum nl80211_dfs_state state; + enum nl80211_radar_event event; +}; + +static bool cfg80211_dfs_chan_expire(struct cfg80211_registered_device *rdev, + struct cfg80211_dfs_chan_expiry *exp, + struct cfg80211_chan_def *chandef) +{ + struct ieee80211_channel *c = exp->chan; + + guard(wiphy)(&rdev->wiphy); + + /* the state may have changed while the wiphy wasn't locked */ + if (c->dfs_state != exp->state || c->dfs_state_entered != exp->entered) + return false; + + c->dfs_state = NL80211_DFS_USABLE; + c->dfs_state_entered = jiffies; + + cfg80211_chandef_create(chandef, c, NL80211_CHAN_NO_HT); + + nl80211_radar_notify(rdev, chandef, exp->event, NULL, GFP_KERNEL); + + return true; +} + void cfg80211_dfs_channels_update_work(struct work_struct *work) { struct delayed_work *delayed_work = to_delayed_work(work); + struct cfg80211_dfs_chan_expiry *expired; struct cfg80211_registered_device *rdev; struct cfg80211_chan_def chandef; struct ieee80211_supported_band *sband; struct ieee80211_channel *c; struct wiphy *wiphy; - bool check_again = false; + bool check_again = false, pre_cac_allowed; unsigned long timeout, next_time = 0; unsigned long time_dfs_update; enum nl80211_radar_event radar_event; + unsigned int n_chans = 0, n_expired = 0; int bandid, i; rdev = container_of(delayed_work, struct cfg80211_registered_device, @@ -1089,6 +1120,25 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work) wiphy = &rdev->wiphy; rtnl_lock(); + wiphy_lock(wiphy); + for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) { + if (wiphy->bands[bandid]) + n_chans += wiphy->bands[bandid]->n_channels; + } + + expired = kvcalloc(n_chans, sizeof(*expired), GFP_KERNEL); + if (!expired) { + /* hmm - retry later */ + queue_delayed_work(cfg80211_wq, + &rdev->dfs_update_channels_wk, + HZ); + wiphy_unlock(wiphy); + rtnl_unlock(); + return; + } + + pre_cac_allowed = regulatory_pre_cac_allowed(wiphy); + for (bandid = 0; bandid < NUM_NL80211_BANDS; bandid++) { sband = wiphy->bands[bandid]; if (!sband) @@ -1108,8 +1158,7 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work) time_dfs_update = IEEE80211_DFS_MIN_NOP_TIME_MS; radar_event = NL80211_RADAR_NOP_FINISHED; } else { - if (regulatory_pre_cac_allowed(wiphy) || - cfg80211_any_wiphy_oper_chan(wiphy, c)) + if (pre_cac_allowed) continue; time_dfs_update = REG_PRE_CAC_EXPIRY_GRACE_MS; @@ -1120,19 +1169,11 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work) msecs_to_jiffies(time_dfs_update); if (time_after_eq(jiffies, timeout)) { - c->dfs_state = NL80211_DFS_USABLE; - c->dfs_state_entered = jiffies; - - cfg80211_chandef_create(&chandef, c, - NL80211_CHAN_NO_HT); - - nl80211_radar_notify(rdev, &chandef, - radar_event, NULL, - GFP_ATOMIC); - - regulatory_propagate_dfs_state(wiphy, &chandef, - c->dfs_state, - radar_event); + expired[n_expired].chan = c; + expired[n_expired].state = c->dfs_state; + expired[n_expired].entered = c->dfs_state_entered; + expired[n_expired].event = radar_event; + n_expired++; continue; } @@ -1143,8 +1184,25 @@ void cfg80211_dfs_channels_update_work(struct work_struct *work) check_again = true; } } + wiphy_unlock(wiphy); + + /* these lock the wiphys, so must be done without holding ours */ + for (i = 0; i < n_expired; i++) { + if (expired[i].event == NL80211_RADAR_PRE_CAC_EXPIRED && + cfg80211_any_wiphy_oper_chan(wiphy, expired[i].chan)) + continue; + + if (!cfg80211_dfs_chan_expire(rdev, &expired[i], &chandef)) + continue; + + regulatory_propagate_dfs_state(wiphy, &chandef, + NL80211_DFS_USABLE, + expired[i].event); + } rtnl_unlock(); + kvfree(expired); + /* reschedule if there are other channels waiting to be cleared again */ if (check_again) queue_delayed_work(cfg80211_wq, &rdev->dfs_update_channels_wk, diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 13d6fc5a56aa..a7602d6d4c64 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -3060,6 +3060,8 @@ static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy) ASSERT_RTNL(); + guard(wiphy)(wiphy); + for_each_rdev(rdev) { if (wiphy == &rdev->wiphy) continue; @@ -4276,7 +4278,7 @@ static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev) struct wireless_dev *wdev; unsigned int link_id; - guard(wiphy)(&rdev->wiphy); + lockdep_assert_wiphy(&rdev->wiphy); /* If we finished CAC or received radar, we should end any * CAC running on the same channels. @@ -4327,6 +4329,8 @@ void regulatory_propagate_dfs_state(struct wiphy *wiphy, chandef->chan->center_freq)) continue; + guard(wiphy)(&rdev->wiphy); + cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state); if (event == NL80211_RADAR_DETECTED ||