It seems we don't have any protection when accessing the key. The RX/TX path may acquire a key which can be freed by the ioctl cmd.
I put a key_lock spinlock to protect all the accesses to the key (whether the sta_info->key or ieee80211_sub_if_data->keys[]). Don't find a good way to handle it :( Thanks, Hong Signed-off-by: Hong Liu <[EMAIL PROTECTED]> diff --git a/net/d80211/ieee80211.c b/net/d80211/ieee80211.c index 751bc76..1a1f6fb 100644 --- a/net/d80211/ieee80211.c +++ b/net/d80211/ieee80211.c @@ -99,12 +99,38 @@ struct ieee80211_key *ieee80211_key_allo return key; } -void ieee80211_key_free(struct ieee80211_key *key) +void ieee80211_key_put(struct ieee80211_key *key) { if (key) kobject_put(&key->kobj); } +void ieee80211_key_get(struct ieee80211_key *key) +{ + if (key) + kobject_get(&key->kobj); +} + +void ieee80211_key_free(struct ieee80211_local *local, + struct ieee80211_key **key, + struct ieee80211_sub_if_data *sdata) +{ + int remove_def = 0; + + if (*key && sdata && sdata->default_key == *key) { + ieee80211_key_sysfs_remove_default(sdata); + remove_def = 1; + } + ieee80211_key_sysfs_remove(*key); + + spin_lock_bh(&local->key_lock); + if (remove_def) + sdata->default_key = NULL; + *key = NULL; + ieee80211_key_put(*key); + spin_unlock_bh(&local->key_lock); +} + void ieee80211_key_release(struct kobject *kobj) { struct ieee80211_key *key; @@ -402,6 +428,7 @@ ieee80211_tx_h_select_key(struct ieee802 else tx->u.tx.control->key_idx = HW_KEY_IDX_INVALID; + spin_lock_bh(&tx->local->key_lock); if (unlikely(tx->u.tx.control->flags & IEEE80211_TXCTL_DO_NOT_ENCRYPT)) tx->key = NULL; else if (tx->sta && tx->sta->key) @@ -410,11 +437,16 @@ ieee80211_tx_h_select_key(struct ieee802 tx->key = tx->sdata->default_key; else if (tx->sdata->drop_unencrypted && !(tx->sdata->eapol && ieee80211_is_eapol(tx->skb))) { + spin_unlock_bh(&tx->local->key_lock); I802_DEBUG_INC(tx->local->tx_handlers_drop_unencrypted); return TXRX_DROP; } else tx->key = NULL; + if (tx->key) + ieee80211_key_get(tx->key); + spin_unlock_bh(&tx->local->key_lock); + if (tx->key) { tx->key->tx_rx_count++; if (unlikely(tx->local->key_tx_rx_threshold && @@ -1216,6 +1248,9 @@ static int ieee80211_tx(struct net_devic skb = tx.skb; /* handlers are allowed to change skb */ + if (tx.key) + ieee80211_key_put(tx.key); + if (sta) sta_info_put(sta); @@ -3092,6 +3127,7 @@ ieee80211_rx_h_check(struct ieee80211_tx else always_sta_key = 1; + spin_lock(&rx->local->key_lock); if (rx->sta && rx->sta->key && always_sta_key) { rx->key = rx->sta->key; } else { @@ -3109,6 +3145,7 @@ ieee80211_rx_h_check(struct ieee80211_tx rx->key = rx->sdata->keys[keyidx]; if (!rx->key) { + spin_unlock(&rx->local->key_lock); if (!rx->u.rx.ra_match) return TXRX_DROP; printk(KERN_DEBUG "%s: RX WEP frame with " @@ -3126,6 +3163,10 @@ ieee80211_rx_h_check(struct ieee80211_tx } } + if (rx->key) + ieee80211_key_get(rx->key); + spin_unlock(&rx->local->key_lock); + if (rx->fc & IEEE80211_FCTL_PROTECTED && rx->key && rx->u.rx.ra_match) { rx->key->tx_rx_count++; if (unlikely(rx->local->key_tx_rx_threshold && @@ -3705,6 +3746,8 @@ void __ieee80211_rx(struct net_device *d } end: + if (rx.key) + ieee80211_key_put(rx.key); if (sta) sta_info_put(sta); } @@ -4411,6 +4454,7 @@ struct net_device *ieee80211_alloc_hw(si init_timer(&local->scan.timer); /* clear it out */ spin_lock_init(&local->generic_lock); + spin_lock_init(&local->key_lock); init_timer(&local->stat_timer); local->stat_timer.function = ieee80211_stat_refresh; local->stat_timer.data = (unsigned long) local; diff --git a/net/d80211/ieee80211_i.h b/net/d80211/ieee80211_i.h index 89666ec..8e2a4a3 100644 --- a/net/d80211/ieee80211_i.h +++ b/net/d80211/ieee80211_i.h @@ -361,6 +361,7 @@ #define IEEE80211_IRQSAFE_QUEUE_LIMIT 12 } ieee80211_msg_enum; spinlock_t generic_lock; + spinlock_t key_lock; /* Station data structures */ struct kset sta_kset; spinlock_t sta_lock; /* mutex for STA data structures */ @@ -568,7 +569,11 @@ ieee80211_key_data2conf(struct ieee80211 struct ieee80211_key *data); struct ieee80211_key *ieee80211_key_alloc(struct ieee80211_sub_if_data *sdata, int idx, size_t key_len, gfp_t flags); -void ieee80211_key_free(struct ieee80211_key *key); +void ieee80211_key_get(struct ieee80211_key *key); +void ieee80211_key_put(struct ieee80211_key *key); +void ieee80211_key_free(struct ieee80211_local *local, + struct ieee80211_key **key, + struct ieee80211_sub_if_data *sdata); void ieee80211_key_release(struct kobject *kobj); void ieee80211_rx_mgmt(struct net_device *dev, struct sk_buff *skb, struct ieee80211_rx_status *status, u32 msg_type); diff --git a/net/d80211/ieee80211_iface.c b/net/d80211/ieee80211_iface.c index 9a187af..4455bd2 100644 --- a/net/d80211/ieee80211_iface.c +++ b/net/d80211/ieee80211_iface.c @@ -239,7 +239,7 @@ #if 0 local->hw->set_key(dev, DISABLE_KEY, addr, local->keys[i], 0); #endif - ieee80211_key_free(sdata->keys[i]); + ieee80211_key_free(local, &sdata->keys[i], NULL); } /* Shouldn't be necessary but won't hurt */ diff --git a/net/d80211/ieee80211_ioctl.c b/net/d80211/ieee80211_ioctl.c index 7179cdc..0ee51da 100644 --- a/net/d80211/ieee80211_ioctl.c +++ b/net/d80211/ieee80211_ioctl.c @@ -521,7 +521,7 @@ static int ieee80211_set_encryption(stru struct ieee80211_local *local = dev->ieee80211_ptr; int ret = 0; struct sta_info *sta; - struct ieee80211_key *key, *old_key; + struct ieee80211_key **key; int try_hwaccel = 1; struct ieee80211_key_conf *keyconf; struct ieee80211_sub_if_data *sdata; @@ -537,7 +537,7 @@ static int ieee80211_set_encryption(stru dev->name, idx); return -EINVAL; } - key = sdata->keys[idx]; + key = &sdata->keys[idx]; /* Disable hwaccel for default keys when the interface is not * the default one. @@ -576,7 +576,7 @@ #endif /* CONFIG_D80211_VERBOSE_DEBUG */ return -ENOENT; } - key = sta->key; + key = &sta->key; } /* FIX: @@ -623,8 +623,8 @@ #endif /* CONFIG_D80211_VERBOSE_DEBUG */ if (alg == ALG_NONE) { keyconf = NULL; - if (try_hwaccel && key && local->hw->set_key && - (keyconf = ieee80211_key_data2conf(local, key)) != NULL && + if (try_hwaccel && *key && local->hw->set_key && + (keyconf = ieee80211_key_data2conf(local, *key)) != NULL && local->hw->set_key(dev, DISABLE_KEY, sta_addr, keyconf, sta ? sta->aid : 0)) { if (err) @@ -635,77 +635,62 @@ #endif /* CONFIG_D80211_VERBOSE_DEBUG */ } kfree(keyconf); - if (key && sdata->default_key == key) { - ieee80211_key_sysfs_remove_default(sdata); - sdata->default_key = NULL; - } - ieee80211_key_sysfs_remove(key); - if (sta) - sta->key = NULL; - else - sdata->keys[idx] = NULL; - ieee80211_key_free(key); - key = NULL; + ieee80211_key_free(local, key, sdata); } else { - old_key = key; - key = ieee80211_key_alloc(sta ? NULL : sdata, idx, key_len, + struct ieee80211_key *tmp; + tmp = ieee80211_key_alloc(sta ? NULL : sdata, idx, key_len, GFP_KERNEL); - if (!key) { + if (!tmp) { ret = -ENOMEM; goto err_out; } /* default to sw encryption; low-level driver sets these if the * requested encryption is supported */ - key->hw_key_idx = HW_KEY_IDX_INVALID; - key->force_sw_encrypt = 1; + tmp->hw_key_idx = HW_KEY_IDX_INVALID; + tmp->force_sw_encrypt = 1; - key->alg = alg; - key->keyidx = idx; - key->keylen = key_len; - memcpy(key->key, _key, key_len); + tmp->alg = alg; + tmp->keyidx = idx; + tmp->keylen = key_len; + memcpy(tmp->key, _key, key_len); if (set_tx_key) - key->default_tx_key = 1; + tmp->default_tx_key = 1; if (alg == ALG_CCMP) { /* Initialize AES key state here as an optimization * so that it does not need to be initialized for every * packet. */ - key->u.ccmp.tfm = ieee80211_aes_key_setup_encrypt( - key->key); - if (!key->u.ccmp.tfm) { + tmp->u.ccmp.tfm = ieee80211_aes_key_setup_encrypt( + tmp->key); + if (!tmp->u.ccmp.tfm) { ret = -ENOMEM; - goto err_free; + kfree(tmp); + goto err_out; } } - if (old_key && sdata->default_key == old_key) { - ieee80211_key_sysfs_remove_default(sdata); - sdata->default_key = NULL; - } - ieee80211_key_sysfs_remove(old_key); - if (sta) - sta->key = key; - else - sdata->keys[idx] = key; - ieee80211_key_free(old_key); + ieee80211_key_free(local, key, sdata); + spin_lock_bh(&local->key_lock); + *key = tmp; + spin_unlock_bh(&local->key_lock); if (sta) - key->kobj.parent = &sta->kobj; - ret = ieee80211_key_sysfs_add(key); + (*key)->kobj.parent = &sta->kobj; + ret = ieee80211_key_sysfs_add(*key); if (ret) goto err_null; if (try_hwaccel && (alg == ALG_WEP || alg == ALG_TKIP || alg == ALG_CCMP)) { int e = ieee80211_set_hw_encryption(dev, sta, sta_addr, - key); + *key); if (err) *err = e; } } - if (set_tx_key || (!sta && !sdata->default_key && key)) { - sdata->default_key = key; + if (set_tx_key || (!sta && !sdata->default_key && *key)) { + sdata->default_key = *key; if (ieee80211_key_sysfs_add_default(sdata)) printk(KERN_WARNING "%s: cannot create symlink to " "default key\n", dev->name); @@ -721,12 +706,7 @@ #endif /* CONFIG_D80211_VERBOSE_DEBUG */ return 0; err_null: - if (sta) - sta->key = NULL; - else - sdata->keys[idx] = NULL; -err_free: - ieee80211_key_free(key); + ieee80211_key_free(local, key, NULL); err_out: if (sta) sta_info_put(sta); @@ -2199,10 +2179,8 @@ static int ieee80211_ioctl_clear_keys(st local->hw->set_key(dev, DISABLE_KEY, addr, keyconf, 0); kfree(keyconf); - ieee80211_key_free(sdata->keys[i]); - sdata->keys[i] = NULL; + ieee80211_key_free(local, &sdata->keys[i], sdata); } - sdata->default_key = NULL; } spin_lock_bh(&local->sta_lock); @@ -2214,8 +2192,7 @@ static int ieee80211_ioctl_clear_keys(st local->hw->set_key(dev, DISABLE_KEY, sta->addr, keyconf, sta->aid); kfree(keyconf); - ieee80211_key_free(sta->key); - sta->key = NULL; + ieee80211_key_free(local, &sta->key, NULL); } spin_unlock_bh(&local->sta_lock); diff --git a/net/d80211/sta_info.c b/net/d80211/sta_info.c index 6e73fab..246ae34 100644 --- a/net/d80211/sta_info.c +++ b/net/d80211/sta_info.c @@ -197,11 +197,7 @@ #ifdef CONFIG_D80211_VERBOSE_DEBUG local->mdev->name, MAC_ARG(sta->addr)); #endif /* CONFIG_D80211_VERBOSE_DEBUG */ - if (sta->key) { - ieee80211_key_sysfs_remove(sta->key); - ieee80211_key_free(sta->key); - sta->key = NULL; - } + ieee80211_key_free(local, &sta->key, NULL); rate_control_remove_sta_attrs(sta, &sta->kobj); ieee80211_sta_sysfs_remove(sta); - To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html