Skip to content

Commit

Permalink
cfg80211: fix BSS struct IE access races
Browse files Browse the repository at this point in the history
When a BSS struct is updated, the IEs are currently
overwritten or freed. This can lead to races if some
other CPU is accessing the BSS struct and using the
IEs concurrently.

Fix this by always allocating the IEs in a new struct
that holds the data and length and protecting access
to this new struct with RCU.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
  • Loading branch information
Johannes Berg committed Nov 30, 2012
1 parent b9a9ada commit 9caf036
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 284 deletions.
9 changes: 8 additions & 1 deletion drivers/net/wireless/libertas/cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ static int lbs_add_common_rates_tlv(u8 *tlv, struct cfg80211_bss *bss)
const u8 *rates_eid, *ext_rates_eid;
int n = 0;

rcu_read_lock();
rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
ext_rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_EXT_SUPP_RATES);

Expand Down Expand Up @@ -325,6 +326,7 @@ static int lbs_add_common_rates_tlv(u8 *tlv, struct cfg80211_bss *bss)
*tlv++ = 0x96;
n = 4;
}
rcu_read_unlock();

rate_tlv->header.len = cpu_to_le16(n);
return sizeof(rate_tlv->header) + n;
Expand Down Expand Up @@ -1140,11 +1142,13 @@ static int lbs_associate(struct lbs_private *priv,
cmd->capability = cpu_to_le16(bss->capability);

/* add SSID TLV */
rcu_read_lock();
ssid_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SSID);
if (ssid_eid)
pos += lbs_add_ssid_tlv(pos, ssid_eid + 2, ssid_eid[1]);
else
lbs_deb_assoc("no SSID\n");
rcu_read_unlock();

/* add DS param TLV */
if (bss->channel)
Expand Down Expand Up @@ -1782,7 +1786,7 @@ static int lbs_ibss_join_existing(struct lbs_private *priv,
struct cfg80211_ibss_params *params,
struct cfg80211_bss *bss)
{
const u8 *rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
const u8 *rates_eid;
struct cmd_ds_802_11_ad_hoc_join cmd;
u8 preamble = RADIO_PREAMBLE_SHORT;
int ret = 0;
Expand Down Expand Up @@ -1841,6 +1845,8 @@ static int lbs_ibss_join_existing(struct lbs_private *priv,

/* set rates to the intersection of our rates and the rates in the
bss */
rcu_read_lock();
rates_eid = ieee80211_bss_get_ie(bss, WLAN_EID_SUPP_RATES);
if (!rates_eid) {
lbs_add_rates(cmd.bss.rates);
} else {
Expand All @@ -1860,6 +1866,7 @@ static int lbs_ibss_join_existing(struct lbs_private *priv,
}
}
}
rcu_read_unlock();

/* Only v8 and below support setting this */
if (MRVL_FW_MAJOR_REV(priv->fwrelease) <= 8) {
Expand Down
35 changes: 26 additions & 9 deletions drivers/net/wireless/mwifiex/sta_ioctl.c
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,22 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv,
struct cfg80211_bss *bss,
struct mwifiex_bssdescriptor *bss_desc)
{
int ret;
int ret, beacon_ie_len;
u8 *beacon_ie;
struct mwifiex_bss_priv *bss_priv = (void *)bss->priv;
const struct cfg80211_bss_ies *ies;

rcu_read_lock();
ies = rcu_dereference(bss->ies);
if (WARN_ON(!ies)) {
/* should never happen */
rcu_read_unlock();
return -EINVAL;
}
beacon_ie = kmemdup(ies->data, ies->len, GFP_ATOMIC);
beacon_ie_len = ies->len;
rcu_read_unlock();

beacon_ie = kmemdup(bss->information_elements, bss->len_beacon_ies,
GFP_KERNEL);
if (!beacon_ie) {
dev_err(priv->adapter->dev, " failed to alloc beacon_ie\n");
return -ENOMEM;
Expand All @@ -172,7 +182,7 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv,
memcpy(bss_desc->mac_address, bss->bssid, ETH_ALEN);
bss_desc->rssi = bss->signal;
bss_desc->beacon_buf = beacon_ie;
bss_desc->beacon_buf_size = bss->len_beacon_ies;
bss_desc->beacon_buf_size = beacon_ie_len;
bss_desc->beacon_period = bss->beacon_interval;
bss_desc->cap_info_bitmap = bss->capability;
bss_desc->bss_band = bss_priv->band;
Expand All @@ -198,18 +208,23 @@ int mwifiex_fill_new_bss_desc(struct mwifiex_private *priv,
static int mwifiex_process_country_ie(struct mwifiex_private *priv,
struct cfg80211_bss *bss)
{
u8 *country_ie, country_ie_len;
const u8 *country_ie;
u8 country_ie_len;
struct mwifiex_802_11d_domain_reg *domain_info =
&priv->adapter->domain_reg;

country_ie = (u8 *)ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY);

if (!country_ie)
rcu_read_lock();
country_ie = ieee80211_bss_get_ie(bss, WLAN_EID_COUNTRY);
if (!country_ie) {
rcu_read_unlock();
return 0;
}

country_ie_len = country_ie[1];
if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) {
rcu_read_unlock();
return 0;
}

domain_info->country_code[0] = country_ie[2];
domain_info->country_code[1] = country_ie[3];
Expand All @@ -223,6 +238,8 @@ static int mwifiex_process_country_ie(struct mwifiex_private *priv,
memcpy((u8 *)domain_info->triplet,
&country_ie[2] + IEEE80211_COUNTRY_STRING_LEN, country_ie_len);

rcu_read_unlock();

if (mwifiex_send_cmd_async(priv, HostCmd_CMD_802_11D_DOMAIN_INFO,
HostCmd_ACT_GEN_SET, 0, NULL)) {
wiphy_err(priv->adapter->wiphy,
Expand Down
41 changes: 27 additions & 14 deletions include/net/cfg80211.h
Original file line number Diff line number Diff line change
Expand Up @@ -1205,6 +1205,18 @@ enum cfg80211_signal_type {
CFG80211_SIGNAL_TYPE_UNSPEC,
};

/**
* struct cfg80211_bss_ie_data - BSS entry IE data
* @rcu_head: internal use, for freeing
* @len: length of the IEs
* @data: IE data
*/
struct cfg80211_bss_ies {
struct rcu_head rcu_head;
int len;
u8 data[];
};

/**
* struct cfg80211_bss - BSS description
*
Expand All @@ -1216,43 +1228,44 @@ enum cfg80211_signal_type {
* @tsf: timestamp of last received update
* @beacon_interval: the beacon interval as from the frame
* @capability: the capability field in host byte order
* @information_elements: the information elements (Note that there
* @ies: the information elements (Note that there
* is no guarantee that these are well-formed!); this is a pointer to
* either the beacon_ies or proberesp_ies depending on whether Probe
* Response frame has been received
* @len_information_elements: total length of the information elements
* @beacon_ies: the information elements from the last Beacon frame
* @len_beacon_ies: total length of the beacon_ies
* @proberesp_ies: the information elements from the last Probe Response frame
* @len_proberesp_ies: total length of the proberesp_ies
* @signal: signal strength value (type depends on the wiphy's signal_type)
* @free_priv: function pointer to free private data
* @priv: private area for driver use, has at least wiphy->bss_priv_size bytes
*/
struct cfg80211_bss {
u64 tsf;

struct ieee80211_channel *channel;

u8 bssid[ETH_ALEN];
u64 tsf;
const struct cfg80211_bss_ies __rcu *ies;
const struct cfg80211_bss_ies __rcu *beacon_ies;
const struct cfg80211_bss_ies __rcu *proberesp_ies;

void (*free_priv)(struct cfg80211_bss *bss);

s32 signal;

u16 beacon_interval;
u16 capability;
u8 *information_elements;
size_t len_information_elements;
u8 *beacon_ies;
size_t len_beacon_ies;
u8 *proberesp_ies;
size_t len_proberesp_ies;

s32 signal;
u8 bssid[ETH_ALEN];

void (*free_priv)(struct cfg80211_bss *bss);
u8 priv[0] __attribute__((__aligned__(sizeof(void *))));
};

/**
* ieee80211_bss_get_ie - find IE with given ID
* @bss: the bss to search
* @ie: the IE ID
*
* Note that the return value is an RCU-protected pointer, so
* rcu_read_lock() must be held when calling this function.
* Returns %NULL if not found.
*/
const u8 *ieee80211_bss_get_ie(struct cfg80211_bss *bss, u8 ie);
Expand Down
Loading

0 comments on commit 9caf036

Please sign in to comment.