Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 21a8e9dd authored by Mohammed Shafi Shajakhan's avatar Mohammed Shafi Shajakhan Committed by Johannes Berg
Browse files

mac80211: Fix possible sband related NULL pointer de-reference

Existing API 'ieee80211_get_sdata_band' returns default 2 GHz band even
if the channel context configuration is NULL. This crashes for chipsets
which support 5 Ghz alone when it tries to access members of 'sband'.
Channel context configuration can be NULL in multivif case and when
channel switch is in progress (or) when it fails. Fix this by replacing
the API 'ieee80211_get_sdata_band' with  'ieee80211_get_sband' which
returns a NULL pointer for sband when the channel configuration is NULL.

An example scenario is as below:

In multivif mode (AP + STA) with drivers like ath10k, when we do a
channel switch in the AP vif (which has a number of clients connected)
and a STA vif which is connected to some other AP, when the channel
switch in AP vif fails, while the STA vifs tries to connect to the
other AP, there is a window where the channel context is NULL/invalid
and this results in a crash  while the clients connected to the AP vif
tries to reconnect and this race is very similar to the one investigated
by Michal in https://patchwork.kernel.org/patch/3788161/

 and this does
happens with hardware that supports 5Ghz alone after long hours of
testing with continuous channel switch on the AP vif

ieee80211 phy0: channel context reservation cannot be finalized because
some interfaces aren't switching
wlan0: failed to finalize CSA, disconnecting
wlan0-1: deauthenticating from 8c:fd:f0:01:54:9c by local choice
	(Reason: 3=DEAUTH_LEAVING)

	WARNING: CPU: 1 PID: 19032 at net/mac80211/ieee80211_i.h:1013 sta_info_alloc+0x374/0x3fc [mac80211]
	[<bf77272c>] (sta_info_alloc [mac80211])
	[<bf78776c>] (ieee80211_add_station [mac80211]))
	[<bf73cc50>] (nl80211_new_station [cfg80211])

	Unable to handle kernel NULL pointer dereference at virtual
	address 00000014
	pgd = d5f4c000
	Internal error: Oops: 17 [#1] PREEMPT SMP ARM
	PC is at sta_info_alloc+0x380/0x3fc [mac80211]
	LR is at sta_info_alloc+0x37c/0x3fc [mac80211]
	[<bf772738>] (sta_info_alloc [mac80211])
	[<bf78776c>] (ieee80211_add_station [mac80211])
	[<bf73cc50>] (nl80211_new_station [cfg80211]))

Cc: Michal Kazior <michal.kazior@tieto.com>
Signed-off-by: default avatarMohammed Shafi Shajakhan <mohammed@qti.qualcomm.com>
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 5fe49a9d
Loading
Loading
Loading
Loading
+17 −13
Original line number Original line Diff line number Diff line
@@ -660,11 +660,12 @@ void sta_set_rate_info_tx(struct sta_info *sta,
		int shift = ieee80211_vif_get_shift(&sta->sdata->vif);
		int shift = ieee80211_vif_get_shift(&sta->sdata->vif);
		u16 brate;
		u16 brate;


		sband = sta->local->hw.wiphy->bands[
		sband = ieee80211_get_sband(sta->sdata);
				ieee80211_get_sdata_band(sta->sdata)];
		if (sband) {
			brate = sband->bitrates[rate->idx].bitrate;
			brate = sband->bitrates[rate->idx].bitrate;
			rinfo->legacy = DIV_ROUND_UP(brate, 1 << shift);
			rinfo->legacy = DIV_ROUND_UP(brate, 1 << shift);
		}
		}
	}
	if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
	if (rate->flags & IEEE80211_TX_RC_40_MHZ_WIDTH)
		rinfo->bw = RATE_INFO_BW_40;
		rinfo->bw = RATE_INFO_BW_40;
	else if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
	else if (rate->flags & IEEE80211_TX_RC_80_MHZ_WIDTH)
@@ -1253,10 +1254,11 @@ static int sta_apply_parameters(struct ieee80211_local *local,
	int ret = 0;
	int ret = 0;
	struct ieee80211_supported_band *sband;
	struct ieee80211_supported_band *sband;
	struct ieee80211_sub_if_data *sdata = sta->sdata;
	struct ieee80211_sub_if_data *sdata = sta->sdata;
	enum nl80211_band band = ieee80211_get_sdata_band(sdata);
	u32 mask, set;
	u32 mask, set;


	sband = local->hw.wiphy->bands[band];
	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return -EINVAL;


	mask = params->sta_flags_mask;
	mask = params->sta_flags_mask;
	set = params->sta_flags_set;
	set = params->sta_flags_set;
@@ -1389,7 +1391,7 @@ static int sta_apply_parameters(struct ieee80211_local *local,
		ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef,
		ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef,
					 sband, params->supported_rates,
					 sband, params->supported_rates,
					 params->supported_rates_len,
					 params->supported_rates_len,
					 &sta->sta.supp_rates[band]);
					 &sta->sta.supp_rates[sband->band]);
	}
	}


	if (params->ht_capa)
	if (params->ht_capa)
@@ -1405,8 +1407,8 @@ static int sta_apply_parameters(struct ieee80211_local *local,
		/* returned value is only needed for rc update, but the
		/* returned value is only needed for rc update, but the
		 * rc isn't initialized here yet, so ignore it
		 * rc isn't initialized here yet, so ignore it
		 */
		 */
		__ieee80211_vht_handle_opmode(sdata, sta,
		__ieee80211_vht_handle_opmode(sdata, sta, params->opmode_notif,
					      params->opmode_notif, band);
					      sband->band);
	}
	}


	if (params->support_p2p_ps >= 0)
	if (params->support_p2p_ps >= 0)
@@ -2044,13 +2046,15 @@ static int ieee80211_change_bss(struct wiphy *wiphy,
				struct bss_parameters *params)
				struct bss_parameters *params)
{
{
	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
	enum nl80211_band band;
	struct ieee80211_supported_band *sband;
	u32 changed = 0;
	u32 changed = 0;


	if (!sdata_dereference(sdata->u.ap.beacon, sdata))
	if (!sdata_dereference(sdata->u.ap.beacon, sdata))
		return -ENOENT;
		return -ENOENT;


	band = ieee80211_get_sdata_band(sdata);
	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return -EINVAL;


	if (params->use_cts_prot >= 0) {
	if (params->use_cts_prot >= 0) {
		sdata->vif.bss_conf.use_cts_prot = params->use_cts_prot;
		sdata->vif.bss_conf.use_cts_prot = params->use_cts_prot;
@@ -2063,7 +2067,7 @@ static int ieee80211_change_bss(struct wiphy *wiphy,
	}
	}


	if (!sdata->vif.bss_conf.use_short_slot &&
	if (!sdata->vif.bss_conf.use_short_slot &&
	    band == NL80211_BAND_5GHZ) {
	    sband->band == NL80211_BAND_5GHZ) {
		sdata->vif.bss_conf.use_short_slot = true;
		sdata->vif.bss_conf.use_short_slot = true;
		changed |= BSS_CHANGED_ERP_SLOT;
		changed |= BSS_CHANGED_ERP_SLOT;
	}
	}
@@ -2076,7 +2080,7 @@ static int ieee80211_change_bss(struct wiphy *wiphy,


	if (params->basic_rates) {
	if (params->basic_rates) {
		ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef,
		ieee80211_parse_bitrates(&sdata->vif.bss_conf.chandef,
					 wiphy->bands[band],
					 wiphy->bands[sband->band],
					 params->basic_rates,
					 params->basic_rates,
					 params->basic_rates_len,
					 params->basic_rates_len,
					 &sdata->vif.bss_conf.basic_rates);
					 &sdata->vif.bss_conf.basic_rates);
+5 −1
Original line number Original line Diff line number Diff line
@@ -992,7 +992,7 @@ static void ieee80211_update_sta_info(struct ieee80211_sub_if_data *sdata,
	enum nl80211_band band = rx_status->band;
	enum nl80211_band band = rx_status->band;
	enum nl80211_bss_scan_width scan_width;
	enum nl80211_bss_scan_width scan_width;
	struct ieee80211_local *local = sdata->local;
	struct ieee80211_local *local = sdata->local;
	struct ieee80211_supported_band *sband = local->hw.wiphy->bands[band];
	struct ieee80211_supported_band *sband;
	bool rates_updated = false;
	bool rates_updated = false;
	u32 supp_rates = 0;
	u32 supp_rates = 0;


@@ -1002,6 +1002,10 @@ static void ieee80211_update_sta_info(struct ieee80211_sub_if_data *sdata,
	if (!ether_addr_equal(mgmt->bssid, sdata->u.ibss.bssid))
	if (!ether_addr_equal(mgmt->bssid, sdata->u.ibss.bssid))
		return;
		return;


	sband = local->hw.wiphy->bands[band];
	if (WARN_ON(!sband))
		return;

	rcu_read_lock();
	rcu_read_lock();
	sta = sta_info_get(sdata, mgmt->sa);
	sta = sta_info_get(sdata, mgmt->sa);


+21 −15
Original line number Original line Diff line number Diff line
@@ -1001,21 +1001,6 @@ sdata_assert_lock(struct ieee80211_sub_if_data *sdata)
	lockdep_assert_held(&sdata->wdev.mtx);
	lockdep_assert_held(&sdata->wdev.mtx);
}
}


static inline enum nl80211_band
ieee80211_get_sdata_band(struct ieee80211_sub_if_data *sdata)
{
	enum nl80211_band band = NL80211_BAND_2GHZ;
	struct ieee80211_chanctx_conf *chanctx_conf;

	rcu_read_lock();
	chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
	if (!WARN_ON(!chanctx_conf))
		band = chanctx_conf->def.chan->band;
	rcu_read_unlock();

	return band;
}

static inline int
static inline int
ieee80211_chandef_get_shift(struct cfg80211_chan_def *chandef)
ieee80211_chandef_get_shift(struct cfg80211_chan_def *chandef)
{
{
@@ -1421,6 +1406,27 @@ IEEE80211_WDEV_TO_SUB_IF(struct wireless_dev *wdev)
	return container_of(wdev, struct ieee80211_sub_if_data, wdev);
	return container_of(wdev, struct ieee80211_sub_if_data, wdev);
}
}


static inline struct ieee80211_supported_band *
ieee80211_get_sband(struct ieee80211_sub_if_data *sdata)
{
	struct ieee80211_local *local = sdata->local;
	struct ieee80211_chanctx_conf *chanctx_conf;
	enum nl80211_band band;

	rcu_read_lock();
	chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);

	if (WARN_ON(!chanctx_conf)) {
		rcu_read_unlock();
		return NULL;
	}

	band = chanctx_conf->def.chan->band;
	rcu_read_unlock();

	return local->hw.wiphy->bands[band];
}

/* this struct represents 802.11n's RA/TID combination */
/* this struct represents 802.11n's RA/TID combination */
struct ieee80211_ra_tid {
struct ieee80211_ra_tid {
	u8 ra[ETH_ALEN];
	u8 ra[ETH_ALEN];
+20 −9
Original line number Original line Diff line number Diff line
@@ -63,6 +63,7 @@ bool mesh_matches_local(struct ieee80211_sub_if_data *sdata,
	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
	u32 basic_rates = 0;
	u32 basic_rates = 0;
	struct cfg80211_chan_def sta_chan_def;
	struct cfg80211_chan_def sta_chan_def;
	struct ieee80211_supported_band *sband;


	/*
	/*
	 * As support for each feature is added, check for matching
	 * As support for each feature is added, check for matching
@@ -83,7 +84,11 @@ bool mesh_matches_local(struct ieee80211_sub_if_data *sdata,
	     (ifmsh->mesh_auth_id == ie->mesh_config->meshconf_auth)))
	     (ifmsh->mesh_auth_id == ie->mesh_config->meshconf_auth)))
		return false;
		return false;


	ieee80211_sta_get_rates(sdata, ie, ieee80211_get_sdata_band(sdata),
	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return false;

	ieee80211_sta_get_rates(sdata, ie, sband->band,
				&basic_rates);
				&basic_rates);


	if (sdata->vif.bss_conf.basic_rates != basic_rates)
	if (sdata->vif.bss_conf.basic_rates != basic_rates)
@@ -399,12 +404,13 @@ static int mesh_add_ds_params_ie(struct ieee80211_sub_if_data *sdata,
int mesh_add_ht_cap_ie(struct ieee80211_sub_if_data *sdata,
int mesh_add_ht_cap_ie(struct ieee80211_sub_if_data *sdata,
		       struct sk_buff *skb)
		       struct sk_buff *skb)
{
{
	struct ieee80211_local *local = sdata->local;
	enum nl80211_band band = ieee80211_get_sdata_band(sdata);
	struct ieee80211_supported_band *sband;
	struct ieee80211_supported_band *sband;
	u8 *pos;
	u8 *pos;


	sband = local->hw.wiphy->bands[band];
	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return -EINVAL;

	if (!sband->ht_cap.ht_supported ||
	if (!sband->ht_cap.ht_supported ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_5 ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_5 ||
@@ -462,12 +468,13 @@ int mesh_add_ht_oper_ie(struct ieee80211_sub_if_data *sdata,
int mesh_add_vht_cap_ie(struct ieee80211_sub_if_data *sdata,
int mesh_add_vht_cap_ie(struct ieee80211_sub_if_data *sdata,
			struct sk_buff *skb)
			struct sk_buff *skb)
{
{
	struct ieee80211_local *local = sdata->local;
	enum nl80211_band band = ieee80211_get_sdata_band(sdata);
	struct ieee80211_supported_band *sband;
	struct ieee80211_supported_band *sband;
	u8 *pos;
	u8 *pos;


	sband = local->hw.wiphy->bands[band];
	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return -EINVAL;

	if (!sband->vht_cap.vht_supported ||
	if (!sband->vht_cap.vht_supported ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_20_NOHT ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_5 ||
	    sdata->vif.bss_conf.chandef.width == NL80211_CHAN_WIDTH_5 ||
@@ -916,12 +923,16 @@ ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata,
	struct cfg80211_csa_settings params;
	struct cfg80211_csa_settings params;
	struct ieee80211_csa_ie csa_ie;
	struct ieee80211_csa_ie csa_ie;
	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
	struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh;
	enum nl80211_band band = ieee80211_get_sdata_band(sdata);
	struct ieee80211_supported_band *sband;
	int err;
	int err;
	u32 sta_flags;
	u32 sta_flags;


	sdata_assert_lock(sdata);
	sdata_assert_lock(sdata);


	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return false;

	sta_flags = IEEE80211_STA_DISABLE_VHT;
	sta_flags = IEEE80211_STA_DISABLE_VHT;
	switch (sdata->vif.bss_conf.chandef.width) {
	switch (sdata->vif.bss_conf.chandef.width) {
	case NL80211_CHAN_WIDTH_20_NOHT:
	case NL80211_CHAN_WIDTH_20_NOHT:
@@ -935,7 +946,7 @@ ieee80211_mesh_process_chnswitch(struct ieee80211_sub_if_data *sdata,


	memset(&params, 0, sizeof(params));
	memset(&params, 0, sizeof(params));
	memset(&csa_ie, 0, sizeof(csa_ie));
	memset(&csa_ie, 0, sizeof(csa_ie));
	err = ieee80211_parse_ch_switch_ie(sdata, elems, band,
	err = ieee80211_parse_ch_switch_ie(sdata, elems, sband->band,
					   sta_flags, sdata->vif.addr,
					   sta_flags, sdata->vif.addr,
					   &csa_ie);
					   &csa_ie);
	if (err < 0)
	if (err < 0)
+26 −11
Original line number Original line Diff line number Diff line
@@ -95,19 +95,23 @@ static inline void mesh_plink_fsm_restart(struct sta_info *sta)
static u32 mesh_set_short_slot_time(struct ieee80211_sub_if_data *sdata)
static u32 mesh_set_short_slot_time(struct ieee80211_sub_if_data *sdata)
{
{
	struct ieee80211_local *local = sdata->local;
	struct ieee80211_local *local = sdata->local;
	enum nl80211_band band = ieee80211_get_sdata_band(sdata);
	struct ieee80211_supported_band *sband;
	struct ieee80211_supported_band *sband = local->hw.wiphy->bands[band];
	struct sta_info *sta;
	struct sta_info *sta;
	u32 erp_rates = 0, changed = 0;
	u32 erp_rates = 0, changed = 0;
	int i;
	int i;
	bool short_slot = false;
	bool short_slot = false;


	if (band == NL80211_BAND_5GHZ) {
	sband = ieee80211_get_sband(sdata);
	if (!sband)
		return changed;

	if (sband->band == NL80211_BAND_5GHZ) {
		/* (IEEE 802.11-2012 19.4.5) */
		/* (IEEE 802.11-2012 19.4.5) */
		short_slot = true;
		short_slot = true;
		goto out;
		goto out;
	} else if (band != NL80211_BAND_2GHZ)
	} else if (sband->band != NL80211_BAND_2GHZ) {
		goto out;
		goto out;
	}


	for (i = 0; i < sband->n_bitrates; i++)
	for (i = 0; i < sband->n_bitrates; i++)
		if (sband->bitrates[i].flags & IEEE80211_RATE_ERP_G)
		if (sband->bitrates[i].flags & IEEE80211_RATE_ERP_G)
@@ -123,7 +127,7 @@ static u32 mesh_set_short_slot_time(struct ieee80211_sub_if_data *sdata)
			continue;
			continue;


		short_slot = false;
		short_slot = false;
		if (erp_rates & sta->sta.supp_rates[band])
		if (erp_rates & sta->sta.supp_rates[sband->band])
			short_slot = true;
			short_slot = true;
		 else
		 else
			break;
			break;
@@ -249,7 +253,15 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
	mgmt->u.action.u.self_prot.action_code = action;
	mgmt->u.action.u.self_prot.action_code = action;


	if (action != WLAN_SP_MESH_PEERING_CLOSE) {
	if (action != WLAN_SP_MESH_PEERING_CLOSE) {
		enum nl80211_band band = ieee80211_get_sdata_band(sdata);
		struct ieee80211_supported_band *sband;
		enum nl80211_band band;

		sband = ieee80211_get_sband(sdata);
		if (!sband) {
			err = -EINVAL;
			goto free;
		}
		band = sband->band;


		/* capability info */
		/* capability info */
		pos = skb_put(skb, 2);
		pos = skb_put(skb, 2);
@@ -395,13 +407,16 @@ static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
			       struct ieee802_11_elems *elems, bool insert)
			       struct ieee802_11_elems *elems, bool insert)
{
{
	struct ieee80211_local *local = sdata->local;
	struct ieee80211_local *local = sdata->local;
	enum nl80211_band band = ieee80211_get_sdata_band(sdata);
	struct ieee80211_supported_band *sband;
	struct ieee80211_supported_band *sband;
	u32 rates, basic_rates = 0, changed = 0;
	u32 rates, basic_rates = 0, changed = 0;
	enum ieee80211_sta_rx_bandwidth bw = sta->sta.bandwidth;
	enum ieee80211_sta_rx_bandwidth bw = sta->sta.bandwidth;


	sband = local->hw.wiphy->bands[band];
	sband = ieee80211_get_sband(sdata);
	rates = ieee80211_sta_get_rates(sdata, elems, band, &basic_rates);
	if (!sband)
		return;

	rates = ieee80211_sta_get_rates(sdata, elems, sband->band,
					&basic_rates);


	spin_lock_bh(&sta->mesh->plink_lock);
	spin_lock_bh(&sta->mesh->plink_lock);
	sta->rx_stats.last_rx = jiffies;
	sta->rx_stats.last_rx = jiffies;
@@ -412,9 +427,9 @@ static void mesh_sta_info_init(struct ieee80211_sub_if_data *sdata,
		goto out;
		goto out;
	sta->mesh->processed_beacon = true;
	sta->mesh->processed_beacon = true;


	if (sta->sta.supp_rates[band] != rates)
	if (sta->sta.supp_rates[sband->band] != rates)
		changed |= IEEE80211_RC_SUPP_RATES_CHANGED;
		changed |= IEEE80211_RC_SUPP_RATES_CHANGED;
	sta->sta.supp_rates[band] = rates;
	sta->sta.supp_rates[sband->band] = rates;


	if (ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
	if (ieee80211_ht_cap_ie_to_sta_ht_cap(sdata, sband,
					      elems->ht_cap_elem, sta))
					      elems->ht_cap_elem, sta))
Loading