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

Commit 43ba7e95 authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville
Browse files

mac80211: atomically check whether STA exists already



When a STA structure is added, it is often checked whether it
already exists before adding it. This, however, isn't done
atomically so there is a race condition that could lead to two
STA structures being added with the same MAC address. This
patch changes sta_info_add() to return an ERR_PTR in case
of failure and adds the failure mode -EEXIST when the STA
already exists.

Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Cc: Luis Carlos Cobo <luisca@cozybit.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent d46e144b
Loading
Loading
Loading
Loading
+2 −9
Original line number Diff line number Diff line
@@ -562,13 +562,6 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
	if (!netif_running(dev))
		return -ENETDOWN;

	/* XXX: get sta belonging to dev */
	sta = sta_info_get(local, mac);
	if (sta) {
		sta_info_put(sta);
		return -EEXIST;
	}

	if (params->vlan) {
		sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);

@@ -579,8 +572,8 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
		sdata = IEEE80211_DEV_TO_SUB_IF(dev);

	sta = sta_info_add(local, dev, mac, GFP_KERNEL);
	if (!sta)
		return -ENOMEM;
	if (IS_ERR(sta))
		return PTR_ERR(sta);

	sta->dev = sdata->dev;
	if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN ||
+2 −2
Original line number Diff line number Diff line
@@ -838,8 +838,8 @@ int ieee80211_if_update_wds(struct net_device *dev, u8 *remote_addr)

	/* Create STA entry for the new peer */
	sta = sta_info_add(local, dev, remote_addr, GFP_KERNEL);
	if (!sta)
		return -ENOMEM;
	if (IS_ERR(sta))
		return PTR_ERR(sta);

	sta->flags |= WLAN_STA_AUTHORIZED;

+3 −3
Original line number Diff line number Diff line
@@ -1807,9 +1807,9 @@ static void ieee80211_rx_mgmt_assoc_resp(struct ieee80211_sub_if_data *sdata,
	if (!sta) {
		struct ieee80211_sta_bss *bss;
		sta = sta_info_add(local, dev, ifsta->bssid, GFP_KERNEL);
		if (!sta) {
		if (IS_ERR(sta)) {
			printk(KERN_DEBUG "%s: failed to add STA entry for the"
			       " AP\n", dev->name);
			       " AP (error %ld)\n", dev->name, PTR_ERR(sta));
			return;
		}
		bss = ieee80211_rx_bss_get(dev, ifsta->bssid,
@@ -3820,7 +3820,7 @@ struct sta_info * ieee80211_ibss_add_sta(struct net_device *dev,
	       wiphy_name(local->hw.wiphy), print_mac(mac, addr), dev->name);

	sta = sta_info_add(local, dev, addr, GFP_ATOMIC);
	if (!sta)
	if (IS_ERR(sta))
		return NULL;

	sta->flags |= WLAN_STA_AUTHORIZED;
+27 −11
Original line number Diff line number Diff line
@@ -55,19 +55,29 @@ static int sta_info_hash_del(struct ieee80211_local *local,
	return -ENOENT;
}

struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr)
/* must hold local->sta_lock */
static struct sta_info *__sta_info_find(struct ieee80211_local *local,
					u8 *addr)
{
	struct sta_info *sta;

	read_lock_bh(&local->sta_lock);
	sta = local->sta_hash[STA_HASH(addr)];
	while (sta) {
		if (memcmp(sta->addr, addr, ETH_ALEN) == 0) {
			__sta_info_get(sta);
		if (compare_ether_addr(sta->addr, addr) == 0)
			break;
		}
		sta = sta->hnext;
	}
	return sta;
}

struct sta_info *sta_info_get(struct ieee80211_local *local, u8 *addr)
{
	struct sta_info *sta;

	read_lock_bh(&local->sta_lock);
	sta = __sta_info_find(local, addr);
	if (sta)
		__sta_info_get(sta);
	read_unlock_bh(&local->sta_lock);

	return sta;
@@ -119,7 +129,7 @@ struct sta_info * sta_info_add(struct ieee80211_local *local,

	sta = kzalloc(sizeof(*sta), gfp);
	if (!sta)
		return NULL;
		return ERR_PTR(-ENOMEM);

	kref_init(&sta->kref);

@@ -128,7 +138,7 @@ struct sta_info * sta_info_add(struct ieee80211_local *local,
	if (!sta->rate_ctrl_priv) {
		rate_control_put(sta->rate_ctrl);
		kfree(sta);
		return NULL;
		return ERR_PTR(-ENOMEM);
	}

	memcpy(sta->addr, addr, ETH_ALEN);
@@ -158,9 +168,15 @@ struct sta_info * sta_info_add(struct ieee80211_local *local,
	}
	skb_queue_head_init(&sta->ps_tx_buf);
	skb_queue_head_init(&sta->tx_filtered);
	__sta_info_get(sta);	/* sta used by caller, decremented by
				 * sta_info_put() */
	write_lock_bh(&local->sta_lock);
	/* mark sta as used (by caller) */
	__sta_info_get(sta);
	/* check if STA exists already */
	if (__sta_info_find(local, addr)) {
		write_unlock_bh(&local->sta_lock);
		sta_info_put(sta);
		return ERR_PTR(-EEXIST);
	}
	list_add(&sta->list, &local->sta_list);
	local->num_sta++;
	sta_info_hash_add(local, sta);
+2 −2

File changed.

Contains only whitespace changes.