mac80211: RCU-ify STA info structure access

This makes access to the STA hash table/list use RCU to protect
against freeing of items. However, it's not a true RCU, the
copy step is missing: whenever somebody changes a STA item it
is simply updated. This is an existing race condition that is
now somewhat understandable.

This patch also fixes the race key freeing vs. STA destruction
by making sure that sta_info_destroy() is always called under
RTNL and frees the key.

Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/ieee80211_ioctl.c b/net/mac80211/ieee80211_ioctl.c
index 38e2d83..5147152 100644
--- a/net/mac80211/ieee80211_ioctl.c
+++ b/net/mac80211/ieee80211_ioctl.c
@@ -33,8 +33,7 @@
 				    size_t key_len)
 {
 	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
-	int ret;
-	struct sta_info *sta = NULL;
+	struct sta_info *sta;
 	struct ieee80211_key *key;
 	struct ieee80211_sub_if_data *sdata;
 
@@ -51,24 +50,23 @@
 			key = sdata->keys[idx];
 		} else {
 			sta = sta_info_get(local, sta_addr);
-			if (!sta) {
-				ret = -ENOENT;
-				key = NULL;
-				goto err_out;
-			}
-
+			if (!sta)
+				return -ENOENT;
 			key = sta->key;
 		}
 
 		if (!key)
-			ret = -ENOENT;
-		else
-			ret = 0;
+			return -ENOENT;
+
+		ieee80211_key_free(key);
+		return 0;
 	} else {
 		key = ieee80211_key_alloc(alg, idx, key_len, _key);
 		if (!key)
 			return -ENOMEM;
 
+		sta = NULL;
+
 		if (!is_broadcast_ether_addr(sta_addr)) {
 			set_tx_key = 0;
 			/*
@@ -78,14 +76,14 @@
 			 * work around this.
 			 */
 			if (idx != 0 && alg != ALG_WEP) {
-				ret = -EINVAL;
-				goto err_out;
+				ieee80211_key_free(key);
+				return -EINVAL;
 			}
 
 			sta = sta_info_get(local, sta_addr);
 			if (!sta) {
-				ret = -ENOENT;
-				goto err_out;
+				ieee80211_key_free(key);
+				return -ENOENT;
 			}
 		}
 
@@ -93,18 +91,9 @@
 
 		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;
+	return 0;
 }
 
 static int ieee80211_ioctl_siwgenie(struct net_device *dev,
@@ -625,7 +614,7 @@
 	else
 		rate->value = 0;
 	rate->value *= 100000;
-	sta_info_put(sta);
+
 	return 0;
 }
 
@@ -1000,7 +989,6 @@
 		wstats->qual.qual = sta->last_signal;
 		wstats->qual.noise = sta->last_noise;
 		wstats->qual.updated = local->wstats_flags;
-		sta_info_put(sta);
 	}
 	return wstats;
 }