Skip to content

Commit

Permalink
cfg80211: validate channel settings across interfaces
Browse files Browse the repository at this point in the history
Currently, there's a problem that affects regulatory
enforcement and connection stability, in that it is
possible to switch the channel while connected to a
network or joined to an IBSS.

The problem comes from the fact that we only validate
the channel against the current interface's type, not
against any other interface. Thus, you have any type
of interface up, additionally bring up a monitor mode
interface and switch the channel on the monitor. This
will obviously also switch the channel on the other
interface.

The problem now is that if you do that while sending
beacons for IBSS mode, you can switch to a disabled
channel or a channel that doesn't allow beaconing.
Combined with a managed mode interface connected to
an AP instead of an IBSS interface, you can easily
break the connection that way.

To fix this, this patch validates any channel change
with all available interfaces, and disallows such
changes on secondary interfaces if another interface
is connected to an AP or joined to an IBSS.

Signed-off-by: Johannes Berg <[email protected]>
Signed-off-by: John W. Linville <[email protected]>
  • Loading branch information
jmberg authored and linvjw committed Aug 14, 2009
1 parent f26b32e commit 59bbb6f
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 131 deletions.
3 changes: 2 additions & 1 deletion net/wireless/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ obj-$(CONFIG_LIB80211_CRYPT_WEP) += lib80211_crypt_wep.o
obj-$(CONFIG_LIB80211_CRYPT_CCMP) += lib80211_crypt_ccmp.o
obj-$(CONFIG_LIB80211_CRYPT_TKIP) += lib80211_crypt_tkip.o

cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o mlme.o ibss.o sme.o
cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o
cfg80211-y += mlme.o ibss.o sme.o chan.o
cfg80211-$(CONFIG_CFG80211_DEBUGFS) += debugfs.o
cfg80211-$(CONFIG_WIRELESS_EXT) += wext-compat.o wext-sme.o

Expand Down
88 changes: 88 additions & 0 deletions net/wireless/chan.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* This file contains helper code to handle channel
* settings and keeping track of what is possible at
* any point in time.
*
* Copyright 2009 Johannes Berg <[email protected]>
*/

#include <net/cfg80211.h>
#include "core.h"

struct ieee80211_channel *
rdev_fixed_channel(struct cfg80211_registered_device *rdev,
struct wireless_dev *for_wdev)
{
struct wireless_dev *wdev;
struct ieee80211_channel *result = NULL;

WARN_ON(!mutex_is_locked(&rdev->devlist_mtx));

list_for_each_entry(wdev, &rdev->netdev_list, list) {
if (wdev == for_wdev)
continue;

/*
* Lock manually to tell lockdep about allowed
* nesting here if for_wdev->mtx is held already.
* This is ok as it's all under the rdev devlist
* mutex and as such can only be done once at any
* given time.
*/
mutex_lock_nested(&wdev->mtx, SINGLE_DEPTH_NESTING);
if (wdev->current_bss)
result = wdev->current_bss->pub.channel;
wdev_unlock(wdev);

if (result)
break;
}

return result;
}

int rdev_set_freq(struct cfg80211_registered_device *rdev,
int freq, enum nl80211_channel_type channel_type)
{
struct ieee80211_channel *chan;
struct ieee80211_sta_ht_cap *ht_cap;
int result;

if (rdev_fixed_channel(rdev, NULL))
return -EBUSY;

if (!rdev->ops->set_channel)
return -EOPNOTSUPP;

chan = ieee80211_get_channel(&rdev->wiphy, freq);

/* Primary channel not allowed */
if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
return -EINVAL;

if (channel_type == NL80211_CHAN_HT40MINUS &&
chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
return -EINVAL;
else if (channel_type == NL80211_CHAN_HT40PLUS &&
chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
return -EINVAL;

ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;

if (channel_type != NL80211_CHAN_NO_HT) {
if (!ht_cap->ht_supported)
return -EINVAL;

if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)
return -EINVAL;
}

result = rdev->ops->set_channel(&rdev->wiphy, chan, channel_type);
if (result)
return result;

rdev->channel = chan;

return 0;
}
6 changes: 6 additions & 0 deletions net/wireless/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,10 @@ void cfg80211_sme_disassoc(struct net_device *dev, int idx);
void __cfg80211_scan_done(struct work_struct *wk);
void cfg80211_upload_connect_keys(struct wireless_dev *wdev);

struct ieee80211_channel *
rdev_fixed_channel(struct cfg80211_registered_device *rdev,
struct wireless_dev *for_wdev);
int rdev_set_freq(struct cfg80211_registered_device *rdev,
int freq, enum nl80211_channel_type channel_type);

#endif /* __NET_WIRELESS_CORE_H */
61 changes: 39 additions & 22 deletions net/wireless/ibss.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,15 @@ int __cfg80211_join_ibss(struct cfg80211_registered_device *rdev,
struct cfg80211_cached_keys *connkeys)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct ieee80211_channel *chan;
int err;

ASSERT_WDEV_LOCK(wdev);

chan = rdev_fixed_channel(rdev, wdev);
if (chan && chan != params->channel)
return -EBUSY;

if (wdev->ssid_len)
return -EALREADY;

Expand Down Expand Up @@ -112,9 +117,11 @@ int cfg80211_join_ibss(struct cfg80211_registered_device *rdev,
struct wireless_dev *wdev = dev->ieee80211_ptr;
int err;

mutex_lock(&rdev->devlist_mtx);
wdev_lock(wdev);
err = __cfg80211_join_ibss(rdev, dev, params, connkeys);
wdev_unlock(wdev);
mutex_unlock(&rdev->devlist_mtx);

return err;
}
Expand Down Expand Up @@ -264,36 +271,40 @@ int cfg80211_ibss_wext_join(struct cfg80211_registered_device *rdev,

int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
struct iw_request_info *info,
struct iw_freq *freq, char *extra)
struct iw_freq *wextfreq, char *extra)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct ieee80211_channel *chan;
int err;
struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
struct ieee80211_channel *chan = NULL;
int err, freq;

/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;

if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
if (!rdev->ops->join_ibss)
return -EOPNOTSUPP;

chan = cfg80211_wext_freq(wdev->wiphy, freq);
if (chan && IS_ERR(chan))
return PTR_ERR(chan);
freq = cfg80211_wext_freq(wdev->wiphy, wextfreq);
if (freq < 0)
return freq;

if (chan &&
(chan->flags & IEEE80211_CHAN_NO_IBSS ||
chan->flags & IEEE80211_CHAN_DISABLED))
return -EINVAL;
if (freq) {
chan = ieee80211_get_channel(wdev->wiphy, freq);
if (!chan)
return -EINVAL;
if (chan->flags & IEEE80211_CHAN_NO_IBSS ||
chan->flags & IEEE80211_CHAN_DISABLED)
return -EINVAL;
}

if (wdev->wext.ibss.channel == chan)
return 0;

wdev_lock(wdev);
err = 0;
if (wdev->ssid_len)
err = __cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy),
dev, true);
err = __cfg80211_leave_ibss(rdev, dev, true);
wdev_unlock(wdev);

if (err)
Expand All @@ -307,9 +318,11 @@ int cfg80211_ibss_wext_siwfreq(struct net_device *dev,
wdev->wext.ibss.channel_fixed = false;
}

mutex_lock(&rdev->devlist_mtx);
wdev_lock(wdev);
err = cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
err = cfg80211_ibss_wext_join(rdev, wdev);
wdev_unlock(wdev);
mutex_unlock(&rdev->devlist_mtx);

return err;
}
Expand Down Expand Up @@ -347,21 +360,21 @@ int cfg80211_ibss_wext_siwessid(struct net_device *dev,
struct iw_point *data, char *ssid)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
size_t len = data->length;
int err;

/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;

if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
if (!rdev->ops->join_ibss)
return -EOPNOTSUPP;

wdev_lock(wdev);
err = 0;
if (wdev->ssid_len)
err = __cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy),
dev, true);
err = __cfg80211_leave_ibss(rdev, dev, true);
wdev_unlock(wdev);

if (err)
Expand All @@ -375,9 +388,11 @@ int cfg80211_ibss_wext_siwessid(struct net_device *dev,
memcpy(wdev->wext.ibss.ssid, ssid, len);
wdev->wext.ibss.ssid_len = len;

mutex_lock(&rdev->devlist_mtx);
wdev_lock(wdev);
err = cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
err = cfg80211_ibss_wext_join(rdev, wdev);
wdev_unlock(wdev);
mutex_unlock(&rdev->devlist_mtx);

return err;
}
Expand Down Expand Up @@ -414,14 +429,15 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
struct sockaddr *ap_addr, char *extra)
{
struct wireless_dev *wdev = dev->ieee80211_ptr;
struct cfg80211_registered_device *rdev = wiphy_to_dev(wdev->wiphy);
u8 *bssid = ap_addr->sa_data;
int err;

/* call only for ibss! */
if (WARN_ON(wdev->iftype != NL80211_IFTYPE_ADHOC))
return -EINVAL;

if (!wiphy_to_dev(wdev->wiphy)->ops->join_ibss)
if (!rdev->ops->join_ibss)
return -EOPNOTSUPP;

if (ap_addr->sa_family != ARPHRD_ETHER)
Expand All @@ -443,8 +459,7 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
wdev_lock(wdev);
err = 0;
if (wdev->ssid_len)
err = __cfg80211_leave_ibss(wiphy_to_dev(wdev->wiphy),
dev, true);
err = __cfg80211_leave_ibss(rdev, dev, true);
wdev_unlock(wdev);

if (err)
Expand All @@ -456,9 +471,11 @@ int cfg80211_ibss_wext_siwap(struct net_device *dev,
} else
wdev->wext.ibss.bssid = NULL;

mutex_lock(&rdev->devlist_mtx);
wdev_lock(wdev);
err = cfg80211_ibss_wext_join(wiphy_to_dev(wdev->wiphy), wdev);
err = cfg80211_ibss_wext_join(rdev, wdev);
wdev_unlock(wdev);
mutex_unlock(&rdev->devlist_mtx);

return err;
}
Expand Down
54 changes: 13 additions & 41 deletions net/wireless/nl80211.c
Original file line number Diff line number Diff line change
Expand Up @@ -701,15 +701,8 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)

if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) {
enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
struct ieee80211_channel *chan;
struct ieee80211_sta_ht_cap *ht_cap;
u32 freq;

if (!rdev->ops->set_channel) {
result = -EOPNOTSUPP;
goto bad_res;
}

result = -EINVAL;

if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
Expand All @@ -723,42 +716,12 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info)
}

freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
chan = ieee80211_get_channel(&rdev->wiphy, freq);

/* Primary channel not allowed */
if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
goto bad_res;

if (channel_type == NL80211_CHAN_HT40MINUS &&
(chan->flags & IEEE80211_CHAN_NO_HT40MINUS))
goto bad_res;
else if (channel_type == NL80211_CHAN_HT40PLUS &&
(chan->flags & IEEE80211_CHAN_NO_HT40PLUS))
goto bad_res;

/*
* At this point we know if that if HT40 was requested
* we are allowed to use it and the extension channel
* exists.
*/

ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;

/* no HT capabilities or intolerant */
if (channel_type != NL80211_CHAN_NO_HT) {
if (!ht_cap->ht_supported)
goto bad_res;
if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
(ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT))
goto bad_res;
}

result = rdev->ops->set_channel(&rdev->wiphy, chan,
channel_type);
mutex_lock(&rdev->devlist_mtx);
result = rdev_set_freq(rdev, freq, channel_type);
mutex_unlock(&rdev->devlist_mtx);
if (result)
goto bad_res;

rdev->channel = chan;
}

changed = 0;
Expand Down Expand Up @@ -3453,7 +3416,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
struct cfg80211_registered_device *rdev;
struct net_device *dev;
struct cfg80211_crypto_settings crypto;
struct ieee80211_channel *chan;
struct ieee80211_channel *chan, *fixedchan;
const u8 *bssid, *ssid, *ie = NULL, *prev_bssid = NULL;
int err, ssid_len, ie_len = 0;
bool use_mfp = false;
Expand Down Expand Up @@ -3496,6 +3459,15 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info)
goto out;
}

mutex_lock(&rdev->devlist_mtx);
fixedchan = rdev_fixed_channel(rdev, NULL);
if (fixedchan && chan != fixedchan) {
err = -EBUSY;
mutex_unlock(&rdev->devlist_mtx);
goto out;
}
mutex_unlock(&rdev->devlist_mtx);

ssid = nla_data(info->attrs[NL80211_ATTR_SSID]);
ssid_len = nla_len(info->attrs[NL80211_ATTR_SSID]);

Expand Down
Loading

0 comments on commit 59bbb6f

Please sign in to comment.