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/cfg.c b/net/mac80211/cfg.c
index 6ac4923..e9ba6fc 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -136,7 +136,6 @@
 	struct ieee80211_sub_if_data *sdata;
 	struct sta_info *sta = NULL;
 	enum ieee80211_key_alg alg;
-	int ret;
 	struct ieee80211_key *key;
 
 	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
@@ -170,12 +169,7 @@
 
 	ieee80211_key_link(key, sdata, sta);
 
-	ret = 0;
-
-	if (sta)
-		sta_info_put(sta);
-
-	return ret;
+	return 0;
 }
 
 static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
@@ -184,7 +178,6 @@
 	struct ieee80211_sub_if_data *sdata;
 	struct sta_info *sta;
 	int ret;
-	struct ieee80211_key *key;
 
 	sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
@@ -195,21 +188,18 @@
 
 		ret = 0;
 		if (sta->key) {
-			key = sta->key;
-			ieee80211_key_free(key);
+			ieee80211_key_free(sta->key);
 			WARN_ON(sta->key);
 		} else
 			ret = -ENOENT;
 
-		sta_info_put(sta);
 		return ret;
 	}
 
 	if (!sdata->keys[key_idx])
 		return -ENOENT;
 
-	key = sdata->keys[key_idx];
-	ieee80211_key_free(key);
+	ieee80211_key_free(sdata->keys[key_idx]);
 	WARN_ON(sdata->keys[key_idx]);
 
 	return 0;
@@ -292,8 +282,6 @@
 	err = 0;
 
  out:
-	if (sta)
-		sta_info_put(sta);
 	return err;
 }
 
@@ -311,7 +299,7 @@
 
 static void sta_set_sinfo(struct sta_info *sta, struct station_info *sinfo)
 {
-	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+	struct ieee80211_sub_if_data *sdata = sta->sdata;
 
 	sinfo->filled = STATION_INFO_INACTIVE_TIME |
 			STATION_INFO_RX_BYTES |
@@ -340,16 +328,20 @@
 {
 	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
 	struct sta_info *sta;
+	int ret = -ENOENT;
+
+	rcu_read_lock();
 
 	sta = sta_info_get_by_idx(local, idx, dev);
-	if (!sta)
-		return -ENOENT;
+	if (sta) {
+		ret = 0;
+		memcpy(mac, sta->addr, ETH_ALEN);
+		sta_set_sinfo(sta, sinfo);
+	}
 
-	memcpy(mac, sta->addr, ETH_ALEN);
-	sta_set_sinfo(sta, sinfo);
-	sta_info_put(sta);
+	rcu_read_unlock();
 
-	return 0;
+	return ret;
 }
 
 static int ieee80211_get_station(struct wiphy *wiphy, struct net_device *dev,
@@ -357,16 +349,21 @@
 {
 	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
 	struct sta_info *sta;
+	int ret = -ENOENT;
 
-	sta = sta_info_get(local, mac);
-	if (!sta)
-		return -ENOENT;
+	rcu_read_lock();
 
 	/* XXX: verify sta->dev == dev */
-	sta_set_sinfo(sta, sinfo);
-	sta_info_put(sta);
 
-	return 0;
+	sta = sta_info_get(local, mac);
+	if (sta) {
+		ret = 0;
+		sta_set_sinfo(sta, sinfo);
+	}
+
+	rcu_read_unlock();
+
+	return ret;
 }
 
 /*
@@ -559,8 +556,8 @@
 	msg->xid_info[1] = 1;	/* LLC types/classes: Type 1 LLC */
 	msg->xid_info[2] = 0;	/* XID sender's receive window size (RW) */
 
-	skb->dev = sta->dev;
-	skb->protocol = eth_type_trans(skb, sta->dev);
+	skb->dev = sta->sdata->dev;
+	skb->protocol = eth_type_trans(skb, sta->sdata->dev);
 	memset(skb->cb, 0, sizeof(skb->cb));
 	netif_rx(skb);
 }
@@ -572,7 +569,7 @@
 	u32 rates;
 	int i, j;
 	struct ieee80211_supported_band *sband;
-	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(sta->dev);
+	struct ieee80211_sub_if_data *sdata = sta->sdata;
 
 	if (params->station_flags & STATION_FLAG_CHANGED) {
 		sta->flags &= ~WLAN_STA_AUTHORIZED;
@@ -644,14 +641,13 @@
 		sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
 	if (ieee80211_vif_is_mesh(&sdata->vif))
-		sta = mesh_plink_add(mac, DEFAULT_RATES, dev);
+		sta = mesh_plink_add(mac, DEFAULT_RATES, sdata);
 	else
-		sta = sta_info_add(local, dev, mac, GFP_KERNEL);
+		sta = sta_info_add(sdata, mac);
 
 	if (IS_ERR(sta))
 		return PTR_ERR(sta);
 
-	sta->dev = sdata->dev;
 	if (sdata->vif.type == IEEE80211_IF_TYPE_VLAN ||
 	    sdata->vif.type == IEEE80211_IF_TYPE_AP)
 		ieee80211_send_layer2_update(sta);
@@ -662,15 +658,14 @@
 
 	rate_control_rate_init(sta, local);
 
-	sta_info_put(sta);
-
 	return 0;
 }
 
 static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
 				 u8 *mac)
 {
-	struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
+	struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+	struct ieee80211_local *local = sdata->local;
 	struct sta_info *sta;
 
 	if (mac) {
@@ -679,10 +674,14 @@
 		if (!sta)
 			return -ENOENT;
 
-		sta_info_free(sta);
-		sta_info_put(sta);
+		sta_info_unlink(&sta);
+
+		if (sta) {
+			synchronize_rcu();
+			sta_info_destroy(sta);
+		}
 	} else
-		sta_info_flush(local, dev);
+		sta_info_flush(local, sdata);
 
 	return 0;
 }
@@ -701,21 +700,19 @@
 	if (!sta)
 		return -ENOENT;
 
-	if (params->vlan && params->vlan != sta->dev) {
+	if (params->vlan && params->vlan != sta->sdata->dev) {
 		vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
 
 		if (vlansdata->vif.type != IEEE80211_IF_TYPE_VLAN ||
 		    vlansdata->vif.type != IEEE80211_IF_TYPE_AP)
 			return -EINVAL;
 
-		sta->dev = params->vlan;
+		sta->sdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
 		ieee80211_send_layer2_update(sta);
 	}
 
 	sta_apply_parameters(local, sta, params);
 
-	sta_info_put(sta);
-
 	return 0;
 }
 
@@ -735,23 +732,26 @@
 	if (sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT)
 		return -ENOTSUPP;
 
+	rcu_read_lock();
 	sta = sta_info_get(local, next_hop);
-	if (!sta)
+	if (!sta) {
+		rcu_read_unlock();
 		return -ENOENT;
+	}
 
 	err = mesh_path_add(dst, dev);
-	if (err)
+	if (err) {
+		rcu_read_unlock();
 		return err;
+	}
 
-	rcu_read_lock();
 	mpath = mesh_path_lookup(dst, dev);
 	if (!mpath) {
 		rcu_read_unlock();
-		sta_info_put(sta);
 		return -ENXIO;
 	}
 	mesh_path_fix_nexthop(mpath, sta);
-	sta_info_put(sta);
+
 	rcu_read_unlock();
 	return 0;
 }
@@ -760,7 +760,7 @@
 				 u8 *dst)
 {
 	if (dst)
-		return mesh_path_del(dst, dev);
+		return mesh_path_del(dst, dev, false);
 
 	mesh_path_flush(dev);
 	return 0;
@@ -781,20 +781,22 @@
 	if (sdata->vif.type != IEEE80211_IF_TYPE_MESH_POINT)
 		return -ENOTSUPP;
 
-	sta = sta_info_get(local, next_hop);
-	if (!sta)
-		return -ENOENT;
-
 	rcu_read_lock();
+
+	sta = sta_info_get(local, next_hop);
+	if (!sta) {
+		rcu_read_unlock();
+		return -ENOENT;
+	}
+
 	mpath = mesh_path_lookup(dst, dev);
 	if (!mpath) {
 		rcu_read_unlock();
-		sta_info_put(sta);
 		return -ENOENT;
 	}
 
 	mesh_path_fix_nexthop(mpath, sta);
-	sta_info_put(sta);
+
 	rcu_read_unlock();
 	return 0;
 }