Skip to content

Commit 37c20b2

Browse files
committed
wifi: cfg80211: fix cqm_config access race
Max Schulze reports crashes with brcmfmac. The reason seems to be a race between userspace removing the CQM config and the driver calling cfg80211_cqm_rssi_notify(), where if the data is freed while cfg80211_cqm_rssi_notify() runs it will crash since it assumes wdev->cqm_config is set. This can't be fixed with a simple non-NULL check since there's nothing we can do for locking easily, so use RCU instead to protect the pointer, but that requires pulling the updates out into an asynchronous worker so they can sleep and call back into the driver. Since we need to change the free anyway, also change it to go back to the old settings if changing the settings fails. Reported-and-tested-by: Max Schulze <[email protected]> Closes: https://lore.kernel.org/r/[email protected] Fixes: 4a4b816 ("cfg80211: Accept multiple RSSI thresholds for CQM") Signed-off-by: Johannes Berg <[email protected]>
1 parent 8ba438e commit 37c20b2

File tree

4 files changed

+75
-42
lines changed

4 files changed

+75
-42
lines changed

include/net/cfg80211.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6013,7 +6013,8 @@ struct wireless_dev {
60136013
} wext;
60146014
#endif
60156015

6016-
struct cfg80211_cqm_config *cqm_config;
6016+
struct wiphy_work cqm_rssi_work;
6017+
struct cfg80211_cqm_config __rcu *cqm_config;
60176018

60186019
struct list_head pmsr_list;
60196020
spinlock_t pmsr_lock;

net/wireless/core.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,16 +1181,11 @@ void wiphy_rfkill_set_hw_state_reason(struct wiphy *wiphy, bool blocked,
11811181
}
11821182
EXPORT_SYMBOL(wiphy_rfkill_set_hw_state_reason);
11831183

1184-
void cfg80211_cqm_config_free(struct wireless_dev *wdev)
1185-
{
1186-
kfree(wdev->cqm_config);
1187-
wdev->cqm_config = NULL;
1188-
}
1189-
11901184
static void _cfg80211_unregister_wdev(struct wireless_dev *wdev,
11911185
bool unregister_netdev)
11921186
{
11931187
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
1188+
struct cfg80211_cqm_config *cqm_config;
11941189
unsigned int link_id;
11951190

11961191
ASSERT_RTNL();
@@ -1227,7 +1222,10 @@ static void _cfg80211_unregister_wdev(struct wireless_dev *wdev,
12271222
kfree_sensitive(wdev->wext.keys);
12281223
wdev->wext.keys = NULL;
12291224
#endif
1230-
cfg80211_cqm_config_free(wdev);
1225+
wiphy_work_cancel(wdev->wiphy, &wdev->cqm_rssi_work);
1226+
/* deleted from the list, so can't be found from nl80211 any more */
1227+
cqm_config = rcu_access_pointer(wdev->cqm_config);
1228+
kfree_rcu(cqm_config, rcu_head);
12311229

12321230
/*
12331231
* Ensure that all events have been processed and
@@ -1379,6 +1377,8 @@ void cfg80211_init_wdev(struct wireless_dev *wdev)
13791377
wdev->wext.connect.auth_type = NL80211_AUTHTYPE_AUTOMATIC;
13801378
#endif
13811379

1380+
wiphy_work_init(&wdev->cqm_rssi_work, cfg80211_cqm_rssi_notify_work);
1381+
13821382
if (wdev->wiphy->flags & WIPHY_FLAG_PS_ON_BY_DEFAULT)
13831383
wdev->ps = true;
13841384
else

net/wireless/core.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,12 +295,17 @@ struct cfg80211_beacon_registration {
295295
};
296296

297297
struct cfg80211_cqm_config {
298+
struct rcu_head rcu_head;
298299
u32 rssi_hyst;
299300
s32 last_rssi_event_value;
301+
enum nl80211_cqm_rssi_threshold_event last_rssi_event_type;
300302
int n_rssi_thresholds;
301303
s32 rssi_thresholds[] __counted_by(n_rssi_thresholds);
302304
};
303305

306+
void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy,
307+
struct wiphy_work *work);
308+
304309
void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
305310

306311
/* free object */
@@ -566,8 +571,6 @@ cfg80211_bss_update(struct cfg80211_registered_device *rdev,
566571
#define CFG80211_DEV_WARN_ON(cond) ({bool __r = (cond); __r; })
567572
#endif
568573

569-
void cfg80211_cqm_config_free(struct wireless_dev *wdev);
570-
571574
void cfg80211_release_pmsr(struct wireless_dev *wdev, u32 portid);
572575
void cfg80211_pmsr_wdev_down(struct wireless_dev *wdev);
573576
void cfg80211_pmsr_free_wk(struct work_struct *work);

net/wireless/nl80211.c

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12815,7 +12815,8 @@ static int nl80211_set_cqm_txe(struct genl_info *info,
1281512815
}
1281612816

1281712817
static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
12818-
struct net_device *dev)
12818+
struct net_device *dev,
12819+
struct cfg80211_cqm_config *cqm_config)
1281912820
{
1282012821
struct wireless_dev *wdev = dev->ieee80211_ptr;
1282112822
s32 last, low, high;
@@ -12824,7 +12825,7 @@ static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
1282412825
int err;
1282512826

1282612827
/* RSSI reporting disabled? */
12827-
if (!wdev->cqm_config)
12828+
if (!cqm_config)
1282812829
return rdev_set_cqm_rssi_range_config(rdev, dev, 0, 0);
1282912830

1283012831
/*
@@ -12833,7 +12834,7 @@ static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
1283312834
* connection is established and enough beacons received to calculate
1283412835
* the average.
1283512836
*/
12836-
if (!wdev->cqm_config->last_rssi_event_value &&
12837+
if (!cqm_config->last_rssi_event_value &&
1283712838
wdev->links[0].client.current_bss &&
1283812839
rdev->ops->get_station) {
1283912840
struct station_info sinfo = {};
@@ -12847,30 +12848,30 @@ static int cfg80211_cqm_rssi_update(struct cfg80211_registered_device *rdev,
1284712848

1284812849
cfg80211_sinfo_release_content(&sinfo);
1284912850
if (sinfo.filled & BIT_ULL(NL80211_STA_INFO_BEACON_SIGNAL_AVG))
12850-
wdev->cqm_config->last_rssi_event_value =
12851+
cqm_config->last_rssi_event_value =
1285112852
(s8) sinfo.rx_beacon_signal_avg;
1285212853
}
1285312854

12854-
last = wdev->cqm_config->last_rssi_event_value;
12855-
hyst = wdev->cqm_config->rssi_hyst;
12856-
n = wdev->cqm_config->n_rssi_thresholds;
12855+
last = cqm_config->last_rssi_event_value;
12856+
hyst = cqm_config->rssi_hyst;
12857+
n = cqm_config->n_rssi_thresholds;
1285712858

1285812859
for (i = 0; i < n; i++) {
1285912860
i = array_index_nospec(i, n);
12860-
if (last < wdev->cqm_config->rssi_thresholds[i])
12861+
if (last < cqm_config->rssi_thresholds[i])
1286112862
break;
1286212863
}
1286312864

1286412865
low_index = i - 1;
1286512866
if (low_index >= 0) {
1286612867
low_index = array_index_nospec(low_index, n);
12867-
low = wdev->cqm_config->rssi_thresholds[low_index] - hyst;
12868+
low = cqm_config->rssi_thresholds[low_index] - hyst;
1286812869
} else {
1286912870
low = S32_MIN;
1287012871
}
1287112872
if (i < n) {
1287212873
i = array_index_nospec(i, n);
12873-
high = wdev->cqm_config->rssi_thresholds[i] + hyst - 1;
12874+
high = cqm_config->rssi_thresholds[i] + hyst - 1;
1287412875
} else {
1287512876
high = S32_MAX;
1287612877
}
@@ -12883,6 +12884,7 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
1288312884
u32 hysteresis)
1288412885
{
1288512886
struct cfg80211_registered_device *rdev = info->user_ptr[0];
12887+
struct cfg80211_cqm_config *cqm_config = NULL, *old;
1288612888
struct net_device *dev = info->user_ptr[1];
1288712889
struct wireless_dev *wdev = dev->ieee80211_ptr;
1288812890
int i, err;
@@ -12900,10 +12902,6 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
1290012902
wdev->iftype != NL80211_IFTYPE_P2P_CLIENT)
1290112903
return -EOPNOTSUPP;
1290212904

12903-
wdev_lock(wdev);
12904-
cfg80211_cqm_config_free(wdev);
12905-
wdev_unlock(wdev);
12906-
1290712905
if (n_thresholds <= 1 && rdev->ops->set_cqm_rssi_config) {
1290812906
if (n_thresholds == 0 || thresholds[0] == 0) /* Disabling */
1290912907
return rdev_set_cqm_rssi_config(rdev, dev, 0, 0);
@@ -12920,9 +12918,10 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
1292012918
n_thresholds = 0;
1292112919

1292212920
wdev_lock(wdev);
12923-
if (n_thresholds) {
12924-
struct cfg80211_cqm_config *cqm_config;
12921+
old = rcu_dereference_protected(wdev->cqm_config,
12922+
lockdep_is_held(&wdev->mtx));
1292512923

12924+
if (n_thresholds) {
1292612925
cqm_config = kzalloc(struct_size(cqm_config, rssi_thresholds,
1292712926
n_thresholds),
1292812927
GFP_KERNEL);
@@ -12937,11 +12936,18 @@ static int nl80211_set_cqm_rssi(struct genl_info *info,
1293712936
flex_array_size(cqm_config, rssi_thresholds,
1293812937
n_thresholds));
1293912938

12940-
wdev->cqm_config = cqm_config;
12939+
rcu_assign_pointer(wdev->cqm_config, cqm_config);
12940+
} else {
12941+
RCU_INIT_POINTER(wdev->cqm_config, NULL);
1294112942
}
1294212943

12943-
err = cfg80211_cqm_rssi_update(rdev, dev);
12944-
12944+
err = cfg80211_cqm_rssi_update(rdev, dev, cqm_config);
12945+
if (err) {
12946+
rcu_assign_pointer(wdev->cqm_config, old);
12947+
kfree_rcu(cqm_config, rcu_head);
12948+
} else {
12949+
kfree_rcu(old, rcu_head);
12950+
}
1294512951
unlock:
1294612952
wdev_unlock(wdev);
1294712953

@@ -19092,28 +19098,50 @@ void cfg80211_cqm_rssi_notify(struct net_device *dev,
1909219098
enum nl80211_cqm_rssi_threshold_event rssi_event,
1909319099
s32 rssi_level, gfp_t gfp)
1909419100
{
19095-
struct sk_buff *msg;
1909619101
struct wireless_dev *wdev = dev->ieee80211_ptr;
19097-
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wdev->wiphy);
19102+
struct cfg80211_cqm_config *cqm_config;
1909819103

1909919104
trace_cfg80211_cqm_rssi_notify(dev, rssi_event, rssi_level);
1910019105

1910119106
if (WARN_ON(rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_LOW &&
1910219107
rssi_event != NL80211_CQM_RSSI_THRESHOLD_EVENT_HIGH))
1910319108
return;
1910419109

19105-
if (wdev->cqm_config) {
19106-
wdev->cqm_config->last_rssi_event_value = rssi_level;
19110+
rcu_read_lock();
19111+
cqm_config = rcu_dereference(wdev->cqm_config);
19112+
if (cqm_config) {
19113+
cqm_config->last_rssi_event_value = rssi_level;
19114+
cqm_config->last_rssi_event_type = rssi_event;
19115+
wiphy_work_queue(wdev->wiphy, &wdev->cqm_rssi_work);
19116+
}
19117+
rcu_read_unlock();
19118+
}
19119+
EXPORT_SYMBOL(cfg80211_cqm_rssi_notify);
19120+
19121+
void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy, struct wiphy_work *work)
19122+
{
19123+
struct wireless_dev *wdev = container_of(work, struct wireless_dev,
19124+
cqm_rssi_work);
19125+
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
19126+
enum nl80211_cqm_rssi_threshold_event rssi_event;
19127+
struct cfg80211_cqm_config *cqm_config;
19128+
struct sk_buff *msg;
19129+
s32 rssi_level;
1910719130

19108-
cfg80211_cqm_rssi_update(rdev, dev);
19131+
wdev_lock(wdev);
19132+
cqm_config = rcu_dereference_protected(wdev->cqm_config,
19133+
lockdep_is_held(&wdev->mtx));
19134+
if (!wdev->cqm_config)
19135+
goto unlock;
1910919136

19110-
if (rssi_level == 0)
19111-
rssi_level = wdev->cqm_config->last_rssi_event_value;
19112-
}
19137+
cfg80211_cqm_rssi_update(rdev, wdev->netdev, cqm_config);
1911319138

19114-
msg = cfg80211_prepare_cqm(dev, NULL, gfp);
19139+
rssi_level = cqm_config->last_rssi_event_value;
19140+
rssi_event = cqm_config->last_rssi_event_type;
19141+
19142+
msg = cfg80211_prepare_cqm(wdev->netdev, NULL, GFP_KERNEL);
1911519143
if (!msg)
19116-
return;
19144+
goto unlock;
1911719145

1911819146
if (nla_put_u32(msg, NL80211_ATTR_CQM_RSSI_THRESHOLD_EVENT,
1911919147
rssi_event))
@@ -19123,14 +19151,15 @@ void cfg80211_cqm_rssi_notify(struct net_device *dev,
1912319151
rssi_level))
1912419152
goto nla_put_failure;
1912519153

19126-
cfg80211_send_cqm(msg, gfp);
19154+
cfg80211_send_cqm(msg, GFP_KERNEL);
1912719155

19128-
return;
19156+
goto unlock;
1912919157

1913019158
nla_put_failure:
1913119159
nlmsg_free(msg);
19160+
unlock:
19161+
wdev_unlock(wdev);
1913219162
}
19133-
EXPORT_SYMBOL(cfg80211_cqm_rssi_notify);
1913419163

1913519164
void cfg80211_cqm_txe_notify(struct net_device *dev,
1913619165
const u8 *peer, u32 num_packets,

0 commit comments

Comments
 (0)