diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index d0d14f621588..08088a2656c7 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1123,13 +1123,11 @@ ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, const struct ieee80211_color_change_settings *cca, struct ieee80211_link_data *link) { - struct probe_resp *new, *old; + struct probe_resp *new; if (!resp || !resp_len) return 1; - old = sdata_dereference(link->u.ap.probe_resp, sdata); - new = kzalloc(sizeof(struct probe_resp) + resp_len, GFP_KERNEL); if (!new) return -ENOMEM; @@ -1144,9 +1142,7 @@ ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata, else if (cca) new->cntdwn_counter_offsets[0] = cca->counter_offset_presp; - rcu_assign_pointer(link->u.ap.probe_resp, new); - if (old) - kfree_rcu(old, rcu_head); + ap_tmpl_replace(link, link->u.ap.probe_resp, new); return 0; } @@ -1157,7 +1153,7 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, struct ieee80211_bss_conf *link_conf, u64 *changed) { - struct fils_discovery_data *new, *old = NULL; + struct fils_discovery_data *new = NULL; struct ieee80211_fils_discovery *fd; /* @@ -1175,20 +1171,15 @@ static int ieee80211_set_fils_discovery(struct ieee80211_sub_if_data *sdata, fd->min_interval = params->min_interval; fd->max_interval = params->max_interval; - old = sdata_dereference(link->u.ap.fils_discovery, sdata); if (params->tmpl && params->tmpl_len) { new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); if (!new) return -ENOMEM; new->len = params->tmpl_len; memcpy(new->data, params->tmpl, params->tmpl_len); - rcu_assign_pointer(link->u.ap.fils_discovery, new); - } else { - RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); } - if (old) - kfree_rcu(old, rcu_head); + ap_tmpl_replace(link, link->u.ap.fils_discovery, new); *changed |= BSS_CHANGED_FILS_DISCOVERY; return 0; @@ -1201,7 +1192,7 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, struct ieee80211_bss_conf *link_conf, u64 *changed) { - struct unsol_bcast_probe_resp_data *new, *old = NULL; + struct unsol_bcast_probe_resp_data *new = NULL; /* * This configuration is only applicable to transmitting BSSes. @@ -1216,21 +1207,15 @@ ieee80211_set_unsol_bcast_probe_resp(struct ieee80211_sub_if_data *sdata, link_conf->unsol_bcast_probe_resp_interval = params->interval; - old = sdata_dereference(link->u.ap.unsol_bcast_probe_resp, sdata); - if (params->tmpl && params->tmpl_len) { new = kzalloc(sizeof(*new) + params->tmpl_len, GFP_KERNEL); if (!new) return -ENOMEM; new->len = params->tmpl_len; memcpy(new->data, params->tmpl, params->tmpl_len); - rcu_assign_pointer(link->u.ap.unsol_bcast_probe_resp, new); - } else { - RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); } - if (old) - kfree_rcu(old, rcu_head); + ap_tmpl_replace(link, link->u.ap.unsol_bcast_probe_resp, new); *changed |= BSS_CHANGED_UNSOL_BCAST_PROBE_RESP; return 0; @@ -1649,12 +1634,9 @@ ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata, _changed |= BSS_CHANGED_FTM_RESPONDER; } - rcu_assign_pointer(link->u.ap.beacon, new); + ap_tmpl_replace(link, link->u.ap.beacon, new); sdata->u.ap.active = true; - if (old) - kfree_rcu(old, rcu_head); - ieee80211_update_ap_bandwidth(link, params); *changed |= _changed; @@ -1956,11 +1938,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, err = drv_start_ap(sdata->local, sdata, link_conf); if (err) { - old = sdata_dereference(link->u.ap.beacon, sdata); - - if (old) - kfree_rcu(old, rcu_head); - RCU_INIT_POINTER(link->u.ap.beacon, NULL); + ap_tmpl_replace(link, link->u.ap.beacon, NULL); if (ieee80211_num_beaconing_links(sdata) == 0) sdata->u.ap.active = false; @@ -2088,16 +2066,8 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev, lockdep_assert_wiphy(local->hw.wiphy); - old_beacon = sdata_dereference(link->u.ap.beacon, sdata); - if (!old_beacon) + if (!sdata_dereference(link->u.ap.beacon, sdata)) return -ENOENT; - old_probe_resp = sdata_dereference(link->u.ap.probe_resp, - sdata); - old_fils_discovery = sdata_dereference(link->u.ap.fils_discovery, - sdata); - old_unsol_bcast_probe_resp = - sdata_dereference(link->u.ap.unsol_bcast_probe_resp, - sdata); old_s1g_short_beacon = sdata_dereference(link->u.ap.s1g_short_beacon, sdata); @@ -2118,10 +2088,20 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev, } /* remove beacon and probe response */ - RCU_INIT_POINTER(link->u.ap.beacon, NULL); - RCU_INIT_POINTER(link->u.ap.probe_resp, NULL); - RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); - RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); + scoped_guard(spinlock, &link->ap_tmpl_lock) { + old_beacon = ap_tmpl_dereference(link, link->u.ap.beacon); + old_probe_resp = ap_tmpl_dereference(link, + link->u.ap.probe_resp); + old_fils_discovery = + ap_tmpl_dereference(link, link->u.ap.fils_discovery); + old_unsol_bcast_probe_resp = + ap_tmpl_dereference(link, + link->u.ap.unsol_bcast_probe_resp); + RCU_INIT_POINTER(link->u.ap.beacon, NULL); + RCU_INIT_POINTER(link->u.ap.probe_resp, NULL); + RCU_INIT_POINTER(link->u.ap.fils_discovery, NULL); + RCU_INIT_POINTER(link->u.ap.unsol_bcast_probe_resp, NULL); + } RCU_INIT_POINTER(link->u.ap.s1g_short_beacon, NULL); kfree_rcu(old_beacon, rcu_head); if (old_probe_resp) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index b571019bd05a..b16ff6a4aacf 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1152,6 +1152,9 @@ struct ieee80211_link_data { struct ieee80211_link_data_ap ap; } u; + /* protects replacing the u.ap template pointers, also without wiphy mutex */ + spinlock_t ap_tmpl_lock; + struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS]; struct ieee80211_bss_conf *conf; @@ -1166,6 +1169,21 @@ struct ieee80211_link_data { #endif }; +#define ap_tmpl_dereference(link, p) \ + rcu_dereference_protected(p, lockdep_is_held(&(link)->ap_tmpl_lock)) + +#define ap_tmpl_replace(link, p, new) \ +do { \ + typeof(ap_tmpl_dereference(link, p)) __old; \ + \ + spin_lock(&(link)->ap_tmpl_lock); \ + __old = rcu_replace_pointer(p, new, \ + lockdep_is_held(&(link)->ap_tmpl_lock));\ + spin_unlock(&(link)->ap_tmpl_lock); \ + if (__old) \ + kfree_rcu(__old, rcu_head); \ +} while (0) + struct ieee80211_sub_if_data { struct list_head list; diff --git a/net/mac80211/link.c b/net/mac80211/link.c index 931950a10508..ce1c95b40cd9 100644 --- a/net/mac80211/link.c +++ b/net/mac80211/link.c @@ -126,6 +126,7 @@ void ieee80211_link_init(struct ieee80211_sub_if_data *sdata, link->ap_power_level = IEEE80211_UNSET_POWER_LEVEL; link->user_power_level = sdata->local->user_power_level; link_conf->txpower = INT_MIN; + spin_lock_init(&link->ap_tmpl_lock); wiphy_work_init(&link->csa.finalize_work, ieee80211_csa_finalize_work);