aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Berg <johannes@sipsolutions.net>2008-02-25 10:27:45 -0500
committerJohn W. Linville <linville@tuxdriver.com>2008-02-29 15:42:04 -0500
commitdb4d1169d0b893bfb7923b6526748fe2c5a7373f (patch)
treeebd5ac06685bacc069b162b31f99b33c6191b4c3
parent6f48422a29714ed92f6136d9e7d3ff39c75607d7 (diff)
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: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r--net/mac80211/cfg.c27
-rw-r--r--net/mac80211/ieee80211_ioctl.c90
-rw-r--r--net/mac80211/ieee80211_key.h26
-rw-r--r--net/mac80211/key.c156
-rw-r--r--net/mac80211/sta_info.c2
5 files changed, 203 insertions, 98 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index b0c41a0cee79..e7535ffc8e1c 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -123,6 +123,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
123 struct sta_info *sta = NULL; 123 struct sta_info *sta = NULL;
124 enum ieee80211_key_alg alg; 124 enum ieee80211_key_alg alg;
125 int ret; 125 int ret;
126 struct ieee80211_key *key;
126 127
127 sdata = IEEE80211_DEV_TO_SUB_IF(dev); 128 sdata = IEEE80211_DEV_TO_SUB_IF(dev);
128 129
@@ -141,16 +142,21 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct net_device *dev,
141 return -EINVAL; 142 return -EINVAL;
142 } 143 }
143 144
145 key = ieee80211_key_alloc(alg, key_idx, params->key_len, params->key);
146 if (!key)
147 return -ENOMEM;
148
144 if (mac_addr) { 149 if (mac_addr) {
145 sta = sta_info_get(sdata->local, mac_addr); 150 sta = sta_info_get(sdata->local, mac_addr);
146 if (!sta) 151 if (!sta) {
152 ieee80211_key_free(key);
147 return -ENOENT; 153 return -ENOENT;
154 }
148 } 155 }
149 156
157 ieee80211_key_link(key, sdata, sta);
158
150 ret = 0; 159 ret = 0;
151 if (!ieee80211_key_alloc(sdata, sta, alg, key_idx,
152 params->key_len, params->key))
153 ret = -ENOMEM;
154 160
155 if (sta) 161 if (sta)
156 sta_info_put(sta); 162 sta_info_put(sta);
@@ -164,6 +170,7 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
164 struct ieee80211_sub_if_data *sdata; 170 struct ieee80211_sub_if_data *sdata;
165 struct sta_info *sta; 171 struct sta_info *sta;
166 int ret; 172 int ret;
173 struct ieee80211_key *key;
167 174
168 sdata = IEEE80211_DEV_TO_SUB_IF(dev); 175 sdata = IEEE80211_DEV_TO_SUB_IF(dev);
169 176
@@ -173,9 +180,11 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
173 return -ENOENT; 180 return -ENOENT;
174 181
175 ret = 0; 182 ret = 0;
176 if (sta->key) 183 if (sta->key) {
177 ieee80211_key_free(sta->key); 184 key = sta->key;
178 else 185 ieee80211_key_free(key);
186 WARN_ON(sta->key);
187 } else
179 ret = -ENOENT; 188 ret = -ENOENT;
180 189
181 sta_info_put(sta); 190 sta_info_put(sta);
@@ -185,7 +194,9 @@ static int ieee80211_del_key(struct wiphy *wiphy, struct net_device *dev,
185 if (!sdata->keys[key_idx]) 194 if (!sdata->keys[key_idx])
186 return -ENOENT; 195 return -ENOENT;
187 196
188 ieee80211_key_free(sdata->keys[key_idx]); 197 key = sdata->keys[key_idx];
198 ieee80211_key_free(key);
199 WARN_ON(sdata->keys[key_idx]);
189 200
190 return 0; 201 return 0;
191} 202}
diff --git a/net/mac80211/ieee80211_ioctl.c b/net/mac80211/ieee80211_ioctl.c
index 54ad07aafe2d..7551db3f3abc 100644
--- a/net/mac80211/ieee80211_ioctl.c
+++ b/net/mac80211/ieee80211_ioctl.c
@@ -33,8 +33,8 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
33 size_t key_len) 33 size_t key_len)
34{ 34{
35 struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); 35 struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr);
36 int ret = 0; 36 int ret;
37 struct sta_info *sta; 37 struct sta_info *sta = NULL;
38 struct ieee80211_key *key; 38 struct ieee80211_key *key;
39 struct ieee80211_sub_if_data *sdata; 39 struct ieee80211_sub_if_data *sdata;
40 40
@@ -46,58 +46,64 @@ static int ieee80211_set_encryption(struct net_device *dev, u8 *sta_addr,
46 return -EINVAL; 46 return -EINVAL;
47 } 47 }
48 48
49 if (is_broadcast_ether_addr(sta_addr)) { 49 if (remove) {
50 sta = NULL; 50 if (is_broadcast_ether_addr(sta_addr)) {
51 key = sdata->keys[idx]; 51 key = sdata->keys[idx];
52 } else { 52 } else {
53 set_tx_key = 0; 53 sta = sta_info_get(local, sta_addr);
54 /* 54 if (!sta) {
55 * According to the standard, the key index of a pairwise 55 ret = -ENOENT;
56 * key must be zero. However, some AP are broken when it 56 key = NULL;
57 * comes to WEP key indices, so we work around this. 57 goto err_out;
58 */ 58 }
59 if (idx != 0 && alg != ALG_WEP) { 59
60 printk(KERN_DEBUG "%s: set_encrypt - non-zero idx for " 60 key = sta->key;
61 "individual key\n", dev->name);
62 return -EINVAL;
63 } 61 }
64 62
65 sta = sta_info_get(local, sta_addr); 63 if (!key)
66 if (!sta) { 64 ret = -ENOENT;
67#ifdef CONFIG_MAC80211_VERBOSE_DEBUG 65 else
68 DECLARE_MAC_BUF(mac); 66 ret = 0;
69 printk(KERN_DEBUG "%s: set_encrypt - unknown addr " 67 } else {
70 "%s\n", 68 key = ieee80211_key_alloc(alg, idx, key_len, _key);
71 dev->name, print_mac(mac, sta_addr)); 69 if (!key)
72#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ 70 return -ENOMEM;
71
72 if (!is_broadcast_ether_addr(sta_addr)) {
73 set_tx_key = 0;
74 /*
75 * According to the standard, the key index of a
76 * pairwise key must be zero. However, some AP are
77 * broken when it comes to WEP key indices, so we
78 * work around this.
79 */
80 if (idx != 0 && alg != ALG_WEP) {
81 ret = -EINVAL;
82 goto err_out;
83 }
73 84
74 return -ENOENT; 85 sta = sta_info_get(local, sta_addr);
86 if (!sta) {
87 ret = -ENOENT;
88 goto err_out;
89 }
75 } 90 }
76 91
77 key = sta->key; 92 ieee80211_key_link(key, sdata, sta);
78 }
79 93
80 if (remove) { 94 if (set_tx_key || (!sta && !sdata->default_key && key))
81 ieee80211_key_free(key); 95 ieee80211_set_default_key(sdata, idx);
96
97 /* don't free key later */
82 key = NULL; 98 key = NULL;
83 } else {
84 /*
85 * Automatically frees any old key if present.
86 */
87 key = ieee80211_key_alloc(sdata, sta, alg, idx, key_len, _key);
88 if (!key) {
89 ret = -ENOMEM;
90 goto err_out;
91 }
92 }
93 99
94 if (set_tx_key || (!sta && !sdata->default_key && key)) 100 ret = 0;
95 ieee80211_set_default_key(sdata, idx); 101 }
96 102
97 ret = 0;
98 err_out: 103 err_out:
99 if (sta) 104 if (sta)
100 sta_info_put(sta); 105 sta_info_put(sta);
106 ieee80211_key_free(key);
101 return ret; 107 return ret;
102} 108}
103 109
diff --git a/net/mac80211/ieee80211_key.h b/net/mac80211/ieee80211_key.h
index fc770e98d47b..d670e6dbfa39 100644
--- a/net/mac80211/ieee80211_key.h
+++ b/net/mac80211/ieee80211_key.h
@@ -13,6 +13,7 @@
13#include <linux/types.h> 13#include <linux/types.h>
14#include <linux/list.h> 14#include <linux/list.h>
15#include <linux/crypto.h> 15#include <linux/crypto.h>
16#include <linux/rcupdate.h>
16#include <net/mac80211.h> 17#include <net/mac80211.h>
17 18
18/* ALG_TKIP 19/* ALG_TKIP
@@ -45,7 +46,19 @@ struct ieee80211_local;
45struct ieee80211_sub_if_data; 46struct ieee80211_sub_if_data;
46struct sta_info; 47struct sta_info;
47 48
48#define KEY_FLAG_UPLOADED_TO_HARDWARE (1<<0) 49/**
50 * enum ieee80211_internal_key_flags - internal key flags
51 *
52 * @KEY_FLAG_UPLOADED_TO_HARDWARE: Indicates that this key is present
53 * in the hardware for TX crypto hardware acceleration.
54 * @KEY_FLAG_REMOVE_FROM_HARDWARE: Indicates to the key code that this
55 * key is present in the hardware (but it cannot be used for
56 * hardware acceleration any more!)
57 */
58enum ieee80211_internal_key_flags {
59 KEY_FLAG_UPLOADED_TO_HARDWARE = BIT(0),
60 KEY_FLAG_REMOVE_FROM_HARDWARE = BIT(1),
61};
49 62
50struct ieee80211_key { 63struct ieee80211_key {
51 struct ieee80211_local *local; 64 struct ieee80211_local *local;
@@ -112,12 +125,17 @@ struct ieee80211_key {
112 struct ieee80211_key_conf conf; 125 struct ieee80211_key_conf conf;
113}; 126};
114 127
115struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata, 128struct ieee80211_key *ieee80211_key_alloc(enum ieee80211_key_alg alg,
116 struct sta_info *sta,
117 enum ieee80211_key_alg alg,
118 int idx, 129 int idx,
119 size_t key_len, 130 size_t key_len,
120 const u8 *key_data); 131 const u8 *key_data);
132/*
133 * Insert a key into data structures (sdata, sta if necessary)
134 * to make it used, free old key.
135 */
136void ieee80211_key_link(struct ieee80211_key *key,
137 struct ieee80211_sub_if_data *sdata,
138 struct sta_info *sta);
121void ieee80211_key_free(struct ieee80211_key *key); 139void ieee80211_key_free(struct ieee80211_key *key);
122void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx); 140void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx);
123void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata); 141void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata);
diff --git a/net/mac80211/key.c b/net/mac80211/key.c
index ed57fb8e82fc..60aaaf471544 100644
--- a/net/mac80211/key.c
+++ b/net/mac80211/key.c
@@ -13,6 +13,7 @@
13#include <linux/etherdevice.h> 13#include <linux/etherdevice.h>
14#include <linux/list.h> 14#include <linux/list.h>
15#include <linux/rcupdate.h> 15#include <linux/rcupdate.h>
16#include <linux/rtnetlink.h>
16#include <net/mac80211.h> 17#include <net/mac80211.h>
17#include "ieee80211_i.h" 18#include "ieee80211_i.h"
18#include "debugfs_key.h" 19#include "debugfs_key.h"
@@ -34,6 +35,10 @@
34 * 35 *
35 * All operations here are called under RTNL so no extra locking is 36 * All operations here are called under RTNL so no extra locking is
36 * required. 37 * required.
38 *
39 * NOTE: This code requires that sta info *destruction* is done under
40 * RTNL, otherwise it can try to access already freed STA structs
41 * when a STA key is being freed.
37 */ 42 */
38 43
39static const u8 bcast_addr[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; 44static 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)
84 key->conf.keyidx, print_mac(mac, addr), ret); 89 key->conf.keyidx, print_mac(mac, addr), ret);
85} 90}
86 91
92static void ieee80211_key_mark_hw_accel_off(struct ieee80211_key *key)
93{
94 if (key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) {
95 key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE;
96 key->flags |= KEY_FLAG_REMOVE_FROM_HARDWARE;
97 }
98}
99
87static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key) 100static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
88{ 101{
89 const u8 *addr; 102 const u8 *addr;
90 int ret; 103 int ret;
91 DECLARE_MAC_BUF(mac); 104 DECLARE_MAC_BUF(mac);
92 105
93 if (!key->local->ops->set_key) 106 if (!key || !key->local->ops->set_key)
94 return; 107 return;
95 108
96 if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE)) 109 if (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) &&
110 !(key->flags & KEY_FLAG_REMOVE_FROM_HARDWARE))
97 return; 111 return;
98 112
99 addr = get_mac_for_key(key); 113 addr = get_mac_for_key(key);
@@ -108,12 +122,11 @@ static void ieee80211_key_disable_hw_accel(struct ieee80211_key *key)
108 wiphy_name(key->local->hw.wiphy), 122 wiphy_name(key->local->hw.wiphy),
109 key->conf.keyidx, print_mac(mac, addr), ret); 123 key->conf.keyidx, print_mac(mac, addr), ret);
110 124
111 key->flags &= ~KEY_FLAG_UPLOADED_TO_HARDWARE; 125 key->flags &= ~(KEY_FLAG_UPLOADED_TO_HARDWARE |
126 KEY_FLAG_REMOVE_FROM_HARDWARE);
112} 127}
113 128
114struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata, 129struct ieee80211_key *ieee80211_key_alloc(enum ieee80211_key_alg alg,
115 struct sta_info *sta,
116 enum ieee80211_key_alg alg,
117 int idx, 130 int idx,
118 size_t key_len, 131 size_t key_len,
119 const u8 *key_data) 132 const u8 *key_data)
@@ -138,10 +151,6 @@ struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
138 key->conf.keylen = key_len; 151 key->conf.keylen = key_len;
139 memcpy(key->conf.key, key_data, key_len); 152 memcpy(key->conf.key, key_data, key_len);
140 153
141 key->local = sdata->local;
142 key->sdata = sdata;
143 key->sta = sta;
144
145 if (alg == ALG_CCMP) { 154 if (alg == ALG_CCMP) {
146 /* 155 /*
147 * Initialize AES key state here as an optimization so that 156 * 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,
154 } 163 }
155 } 164 }
156 165
157 ieee80211_debugfs_key_add(key->local, key); 166 return key;
167}
158 168
159 /* remove key first */ 169static void __ieee80211_key_replace(struct ieee80211_sub_if_data *sdata,
160 if (sta) 170 struct sta_info *sta,
161 ieee80211_key_free(sta->key); 171 struct ieee80211_key *key,
162 else 172 struct ieee80211_key *new)
163 ieee80211_key_free(sdata->keys[idx]); 173{
174 int idx, defkey;
175
176 if (sta) {
177 rcu_assign_pointer(sta->key, new);
178 } else {
179 WARN_ON(new && key && new->conf.keyidx != key->conf.keyidx);
180
181 if (key)
182 idx = key->conf.keyidx;
183 else
184 idx = new->conf.keyidx;
185
186 defkey = key && sdata->default_key == key;
187
188 if (defkey && !new)
189 ieee80211_set_default_key(sdata, -1);
190
191 rcu_assign_pointer(sdata->keys[idx], new);
192
193 if (defkey && new)
194 ieee80211_set_default_key(sdata, new->conf.keyidx);
195 }
196
197 if (key) {
198 ieee80211_key_mark_hw_accel_off(key);
199 list_del(&key->list);
200 }
201}
202
203void ieee80211_key_link(struct ieee80211_key *key,
204 struct ieee80211_sub_if_data *sdata,
205 struct sta_info *sta)
206{
207 struct ieee80211_key *old_key;
208 int idx;
209
210 ASSERT_RTNL();
211 might_sleep();
212
213 BUG_ON(!sdata);
214 BUG_ON(!key);
215
216 idx = key->conf.keyidx;
217 key->local = sdata->local;
218 key->sdata = sdata;
219 key->sta = sta;
220
221 ieee80211_debugfs_key_add(key->local, key);
164 222
165 if (sta) { 223 if (sta) {
166 ieee80211_debugfs_key_sta_link(key, sta); 224 ieee80211_debugfs_key_sta_link(key, sta);
@@ -186,50 +244,53 @@ struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata,
186 } 244 }
187 } 245 }
188 246
189 /* enable hwaccel if appropriate */
190 if (netif_running(key->sdata->dev))
191 ieee80211_key_enable_hw_accel(key);
192
193 if (sta) 247 if (sta)
194 rcu_assign_pointer(sta->key, key); 248 old_key = sta->key;
195 else 249 else
196 rcu_assign_pointer(sdata->keys[idx], key); 250 old_key = sdata->keys[idx];
251
252 __ieee80211_key_replace(sdata, sta, old_key, key);
197 253
198 list_add(&key->list, &sdata->key_list); 254 list_add(&key->list, &sdata->key_list);
199 255
200 return key; 256 synchronize_rcu();
257
258 ieee80211_key_free(old_key);
259 ieee80211_key_enable_hw_accel(key);
201} 260}
202 261
203void ieee80211_key_free(struct ieee80211_key *key) 262void ieee80211_key_free(struct ieee80211_key *key)
204{ 263{
264 ASSERT_RTNL();
265 might_sleep();
266
205 if (!key) 267 if (!key)
206 return; 268 return;
207 269
208 if (key->sta) { 270 if (key->sdata) {
209 rcu_assign_pointer(key->sta->key, NULL); 271 /*
210 } else { 272 * Replace key with nothingness.
211 if (key->sdata->default_key == key) 273 *
212 ieee80211_set_default_key(key->sdata, -1); 274 * Because other code may have key reference (RCU protected)
213 if (key->conf.keyidx >= 0 && 275 * right now, we then wait for a grace period before freeing
214 key->conf.keyidx < NUM_DEFAULT_KEYS) 276 * it.
215 rcu_assign_pointer(key->sdata->keys[key->conf.keyidx], 277 */
216 NULL); 278 __ieee80211_key_replace(key->sdata, key->sta, key, NULL);
217 else
218 WARN_ON(1);
219 }
220 279
221 /* wait for all key users to complete */ 280 synchronize_rcu();
222 synchronize_rcu();
223 281
224 /* remove from hwaccel if appropriate */ 282 /*
225 ieee80211_key_disable_hw_accel(key); 283 * Remove from hwaccel if appropriate, this will
284 * only happen when the key is actually unlinked,
285 * it will already be done when the key was replaced.
286 */
287 ieee80211_key_disable_hw_accel(key);
288 }
226 289
227 if (key->conf.alg == ALG_CCMP) 290 if (key->conf.alg == ALG_CCMP)
228 ieee80211_aes_key_free(key->u.ccmp.tfm); 291 ieee80211_aes_key_free(key->u.ccmp.tfm);
229 ieee80211_debugfs_key_remove(key); 292 ieee80211_debugfs_key_remove(key);
230 293
231 list_del(&key->list);
232
233 kfree(key); 294 kfree(key);
234} 295}
235 296
@@ -253,6 +314,10 @@ void ieee80211_set_default_key(struct ieee80211_sub_if_data *sdata, int idx)
253void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata) 314void ieee80211_free_keys(struct ieee80211_sub_if_data *sdata)
254{ 315{
255 struct ieee80211_key *key, *tmp; 316 struct ieee80211_key *key, *tmp;
317 LIST_HEAD(tmp_list);
318
319 ASSERT_RTNL();
320 might_sleep();
256 321
257 list_for_each_entry_safe(key, tmp, &sdata->key_list, list) 322 list_for_each_entry_safe(key, tmp, &sdata->key_list, list)
258 ieee80211_key_free(key); 323 ieee80211_key_free(key);
@@ -262,8 +327,10 @@ void ieee80211_enable_keys(struct ieee80211_sub_if_data *sdata)
262{ 327{
263 struct ieee80211_key *key; 328 struct ieee80211_key *key;
264 329
265 WARN_ON(!netif_running(sdata->dev)); 330 ASSERT_RTNL();
266 if (!netif_running(sdata->dev)) 331 might_sleep();
332
333 if (WARN_ON(!netif_running(sdata->dev)))
267 return; 334 return;
268 335
269 list_for_each_entry(key, &sdata->key_list, list) 336 list_for_each_entry(key, &sdata->key_list, list)
@@ -274,6 +341,9 @@ void ieee80211_disable_keys(struct ieee80211_sub_if_data *sdata)
274{ 341{
275 struct ieee80211_key *key; 342 struct ieee80211_key *key;
276 343
344 ASSERT_RTNL();
345 might_sleep();
346
277 list_for_each_entry(key, &sdata->key_list, list) 347 list_for_each_entry(key, &sdata->key_list, list)
278 ieee80211_key_disable_hw_accel(key); 348 ieee80211_key_disable_hw_accel(key);
279} 349}
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index c6c0df4bbd2c..e384e6632d97 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -312,7 +312,7 @@ void sta_info_free(struct sta_info *sta)
312#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */ 312#endif /* CONFIG_MAC80211_VERBOSE_DEBUG */
313 313
314 ieee80211_key_free(sta->key); 314 ieee80211_key_free(sta->key);
315 sta->key = NULL; 315 WARN_ON(sta->key);
316 316
317 if (local->ops->sta_notify) { 317 if (local->ops->sta_notify) {
318 struct ieee80211_sub_if_data *sdata; 318 struct ieee80211_sub_if_data *sdata;