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

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

mac80211: split ieee80211_key_alloc/free



In order to RCU-ify sta_info, we need to be able to allocate
a key without linking it to an sdata/sta structure (because
allocation cannot be done in an rcu critical section). This
patch splits up ieee80211_key_alloc() and updates all users
appropriately.

While at it, this patch fixes a number of race conditions
such as finally making key replacement atomic, unfortunately
at the expense of more complex code.

Note that this patch documents /existing/ bugs with sta info
and key interaction, there is currently a race condition
when a sta info is freed without holding the RTNL. This will
finally be fixed by a followup patch.

Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 6f48422a
Loading
Loading
Loading
Loading
+19 −8
Original line number Diff line number Diff line
@@ -123,6 +123,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
	struct sta_info *sta = NULL;
	enum ieee80211_key_alg alg;
	int ret;
	struct ieee80211_key *key;

	sdata = IEEE80211_DEV_TO_SUB_IF(dev);

@@ -141,16 +142,21 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
		return -EINVAL;
	}

	key = ieee80211_key_alloc(alg, key_idx, params->key_len, params->key);
	if (!key)
		return -ENOMEM;

	if (mac_addr) {
		sta = sta_info_get(sdata->local, mac_addr);
		if (!sta)
		if (!sta) {
			ieee80211_key_free(key);
			return -ENOENT;
		}
	}

	ieee80211_key_link(key, sdata, sta);

	ret = 0;
	if (!ieee80211_key_alloc(sdata, sta, alg, key_idx,
				 params->key_len, params->key))
		ret = -ENOMEM;

	if (sta)
		sta_info_put(sta);
@@ -164,6 +170,7 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
	struct ieee80211_sub_if_data *sdata;
	struct sta_info *sta;
	int ret;
	struct ieee80211_key *key;

	sdata = IEEE80211_DEV_TO_SUB_IF(dev);

@@ -173,9 +180,11 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
			return -ENOENT;

		ret = 0;
		if (sta->key)
			ieee80211_key_free(sta->key);
		else
		if (sta->key) {
			key = sta->key;
			ieee80211_key_free(key);
			WARN_ON(sta->key);
		} else
			ret = -ENOENT;

		sta_info_put(sta);
@@ -185,7 +194,9 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
	if (!sdata->keys[key_idx])
		return -ENOENT;

	ieee80211_key_free(sdata->keys[key_idx]);
	key = sdata->keys[key_idx];
	ieee80211_key_free(key);
	WARN_ON(sdata->keys[key_idx]);

	return 0;
}
+48 −42
Original line number Diff line number Diff line
@@ -33,8 +33,8 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
				    size_t key_len)
{
	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
	int ret = 0;
	struct sta_info *sta;
	int ret;
	struct sta_info *sta = NULL;
	struct ieee80211_key *key;
	struct ieee80211_sub_if_data *sdata;

@@ -46,58 +46,64 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
		return -EINVAL;
	}

	if (remove) {
		if (is_broadcast_ether_addr(sta_addr)) {
		sta = NULL;
			key = sdata->keys[idx];
		} else {
		set_tx_key = 0;
		/*
		 * According to the standard, the key index of a pairwise
		 * key must be zero. However, some AP are broken when it
		 * comes to WEP key indices, so we work around this.
		 */
		if (idx != 0 && alg != ALG_WEP) {
			printk(KERN_DEBUG "%s: set_encrypt - non-zero idx for "
			       "individual key\n", dev->name);
			return -EINVAL;
		}

			sta = sta_info_get(local, sta_addr);
			if (!sta) {
#ifdef CONFIG_MAC80211_VERBOSE_DEBUG
			DECLARE_MAC_BUF(mac);
			printk(KERN_DEBUG "%s: set_encrypt - unknown addr "
			       "%s\n",
			       dev->name, print_mac(mac, sta_addr));
#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */

			return -ENOENT;
				ret = -ENOENT;
				key = NULL;
				goto err_out;
			}

			key = sta->key;
		}

	if (remove) {
		ieee80211_key_free(key);
		key = NULL;
		if (!key)
			ret = -ENOENT;
		else
			ret = 0;
	} else {
		key = ieee80211_key_alloc(alg, idx, key_len, _key);
		if (!key)
			return -ENOMEM;

		if (!is_broadcast_ether_addr(sta_addr)) {
			set_tx_key = 0;
			/*
		 * Automatically frees any old key if present.
			 * According to the standard, the key index of a
			 * pairwise key must be zero. However, some AP are
			 * broken when it comes to WEP key indices, so we
			 * work around this.
			 */
		key = ieee80211_key_alloc(sdata, sta, alg, idx, key_len, _key);
		if (!key) {
			ret = -ENOMEM;
			if (idx != 0 && alg != ALG_WEP) {
				ret = -EINVAL;
				goto err_out;
			}

			sta = sta_info_get(local, sta_addr);
			if (!sta) {
				ret = -ENOENT;
				goto err_out;
			}
		}

		ieee80211_key_link(key, sdata, sta);

		if (set_tx_key || (!sta && !sdata->default_key && key))
			ieee80211_set_default_key(sdata, idx);

		/* don't free key later */
		key = NULL;

		ret = 0;
	}

 err_out:
	if (sta)
		sta_info_put(sta);
	ieee80211_key_free(key);
	return ret;
}

+22 −4
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <linux/types.h>
#include <linux/list.h>
#include <linux/crypto.h>
#include <linux/rcupdate.h>
#include <net/mac80211.h>

/* ALG_TKIP
@@ -45,7 +46,19 @@ struct ieee80211_local;
struct ieee80211_sub_if_data;
struct sta_info;

#define KEY_FLAG_UPLOADED_TO_HARDWARE	(1<<0)
/**
 * enum ieee80211_internal_key_flags - internal key flags
 *
 * @KEY_FLAG_UPLOADED_TO_HARDWARE: Indicates that this key is present
 *	in the hardware for TX crypto hardware acceleration.
 * @KEY_FLAG_REMOVE_FROM_HARDWARE: Indicates to the key code that this
 *	key is present in the hardware (but it cannot be used for
 *	hardware acceleration any more!)
 */
enum ieee80211_internal_key_flags {
	KEY_FLAG_UPLOADED_TO_HARDWARE	= BIT(0),
	KEY_FLAG_REMOVE_FROM_HARDWARE	= BIT(1),
};

struct ieee80211_key {
	struct ieee80211_local *local;
@@ -112,12 +125,17 @@ struct ieee80211_key {
	struct ieee80211_key_conf conf;
};

struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
					  struct sta_info *sta,
					  enum ieee80211_key_alg alg,
struct ieee80211_key *ieee80211_key_alloc(enum ieee80211_key_alg alg,
					  int idx,
					  size_t key_len,
					  const u8 *key_data);
/*
 * Insert a key into data structures (sdata, sta if necessary)
 * to make it used, free old key.
 */
void ieee80211_key_link(struct ieee80211_key *key,
			struct ieee80211_sub_if_data *sdata,
			struct sta_info *sta);
void ieee80211_key_free(struct ieee80211_key *key);
void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx);
void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata);
+113 −43
Original line number Diff line number Diff line
@@ -13,6 +13,7 @@
#include <linux/etherdevice.h>
#include <linux/list.h>
#include <linux/rcupdate.h>
#include <linux/rtnetlink.h>
#include <net/mac80211.h>
#include "ieee80211_i.h"
#include "debugfs_key.h"
@@ -34,6 +35,10 @@
 *
 * All operations here are called under RTNL so no extra locking is
 * required.
 *
 * NOTE: This code requires that sta info *destruction* is done under
 *	 RTNL, otherwise it can try to access already freed STA structs
 *	 when a STA key is being freed.
 */

static const u8 bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
@@ -84,16 +89,25 @@ static void ieee80211_key_enable_hw_accel(struct ieee80211_key *key)
		       key->conf.keyidx, print_mac(mac, addr), ret);
}

static void ieee80211_key_mark_hw_accel_off(struct ieee80211_key *key)
{
	if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
		key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
		key->flags |= KEY_FLAG_REMOVE_FROM_HARDWARE;
	}
}

static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
{
	const u8 *addr;
	int ret;
	DECLARE_MAC_BUF(mac);

	if (!key->local->ops->set_key)
	if (!key || !key->local->ops->set_key)
		return;

	if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE))
	if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
	    !(key->flags & KEY_FLAG_REMOVE_FROM_HARDWARE))
		return;

	addr = get_mac_for_key(key);
@@ -108,12 +122,11 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
		       wiphy_name(key->local->hw.wiphy),
		       key->conf.keyidx, print_mac(mac, addr), ret);

	key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
	key->flags &= ~(KEY_FLAG_UPLOADED_TO_HARDWARE |
			KEY_FLAG_REMOVE_FROM_HARDWARE);
}

struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
					  struct sta_info *sta,
					  enum ieee80211_key_alg alg,
struct ieee80211_key *ieee80211_key_alloc(enum ieee80211_key_alg alg,
					  int idx,
					  size_t key_len,
					  const u8 *key_data)
@@ -138,10 +151,6 @@ struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
	key->conf.keylen = key_len;
	memcpy(key->conf.key, key_data, key_len);

	key->local = sdata->local;
	key->sdata = sdata;
	key->sta = sta;

	if (alg == ALG_CCMP) {
		/*
		 * Initialize AES key state here as an optimization so that
@@ -154,13 +163,62 @@ struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
		}
	}

	ieee80211_debugfs_key_add(key->local, key);
	return key;
}

	/* remove key first */
	if (sta)
		ieee80211_key_free(sta->key);
static void __ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
				    struct sta_info *sta,
				    struct ieee80211_key *key,
				    struct ieee80211_key *new)
{
	int idx, defkey;

	if (sta) {
		rcu_assign_pointer(sta->key, new);
	} else {
		WARN_ON(new && key && new->conf.keyidx != key->conf.keyidx);

		if (key)
			idx = key->conf.keyidx;
		else
		ieee80211_key_free(sdata->keys[idx]);
			idx = new->conf.keyidx;

		defkey = key && sdata->default_key == key;

		if (defkey && !new)
			ieee80211_set_default_key(sdata, -1);

		rcu_assign_pointer(sdata->keys[idx], new);

		if (defkey && new)
			ieee80211_set_default_key(sdata, new->conf.keyidx);
	}

	if (key) {
		ieee80211_key_mark_hw_accel_off(key);
		list_del(&key->list);
	}
}

void ieee80211_key_link(struct ieee80211_key *key,
			struct ieee80211_sub_if_data *sdata,
			struct sta_info *sta)
{
	struct ieee80211_key *old_key;
	int idx;

	ASSERT_RTNL();
	might_sleep();

	BUG_ON(!sdata);
	BUG_ON(!key);

	idx = key->conf.keyidx;
	key->local = sdata->local;
	key->sdata = sdata;
	key->sta = sta;

	ieee80211_debugfs_key_add(key->local, key);

	if (sta) {
		ieee80211_debugfs_key_sta_link(key, sta);
@@ -186,50 +244,53 @@ struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
		}
	}

	/* enable hwaccel if appropriate */
	if (netif_running(key->sdata->dev))
		ieee80211_key_enable_hw_accel(key);

	if (sta)
		rcu_assign_pointer(sta->key, key);
		old_key = sta->key;
	else
		rcu_assign_pointer(sdata->keys[idx], key);
		old_key = sdata->keys[idx];

	__ieee80211_key_replace(sdata, sta, old_key, key);

	list_add(&key->list, &sdata->key_list);

	return key;
	synchronize_rcu();

	ieee80211_key_free(old_key);
	ieee80211_key_enable_hw_accel(key);
}

void ieee80211_key_free(struct ieee80211_key *key)
{
	ASSERT_RTNL();
	might_sleep();

	if (!key)
		return;

	if (key->sta) {
		rcu_assign_pointer(key->sta->key, NULL);
	} else {
		if (key->sdata->default_key == key)
			ieee80211_set_default_key(key->sdata, -1);
		if (key->conf.keyidx >= 0 &&
		    key->conf.keyidx < NUM_DEFAULT_KEYS)
			rcu_assign_pointer(key->sdata->keys[key->conf.keyidx],
					   NULL);
		else
			WARN_ON(1);
	}
	if (key->sdata) {
		/*
		 * Replace key with nothingness.
		 *
		 * Because other code may have key reference (RCU protected)
		 * right now, we then wait for a grace period before freeing
		 * it.
		 */
		__ieee80211_key_replace(key->sdata, key->sta, key, NULL);

	/* wait for all key users to complete */
		synchronize_rcu();

	/* remove from hwaccel if appropriate */
		/*
		 * Remove from hwaccel if appropriate, this will
		 * only happen when the key is actually unlinked,
		 * it will already be done when the key was replaced.
		 */
		ieee80211_key_disable_hw_accel(key);
	}

	if (key->conf.alg == ALG_CCMP)
		ieee80211_aes_key_free(key->u.ccmp.tfm);
	ieee80211_debugfs_key_remove(key);

	list_del(&key->list);

	kfree(key);
}

@@ -253,6 +314,10 @@ void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx)
void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
{
	struct ieee80211_key *key, *tmp;
	LIST_HEAD(tmp_list);

	ASSERT_RTNL();
	might_sleep();

	list_for_each_entry_safe(key, tmp, &sdata->key_list, list)
		ieee80211_key_free(key);
@@ -262,8 +327,10 @@ void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
{
	struct ieee80211_key *key;

	WARN_ON(!netif_running(sdata->dev));
	if (!netif_running(sdata->dev))
	ASSERT_RTNL();
	might_sleep();

	if (WARN_ON(!netif_running(sdata->dev)))
		return;

	list_for_each_entry(key, &sdata->key_list, list)
@@ -274,6 +341,9 @@ void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
{
	struct ieee80211_key *key;

	ASSERT_RTNL();
	might_sleep();

	list_for_each_entry(key, &sdata->key_list, list)
		ieee80211_key_disable_hw_accel(key);
}
+1 −1
Original line number Diff line number Diff line
@@ -312,7 +312,7 @@ void sta_info_free(struct sta_info *sta)
#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */

	ieee80211_key_free(sta->key);
	sta->key = NULL;
	WARN_ON(sta->key);

	if (local->ops->sta_notify) {
		struct ieee80211_sub_if_data *sdata;