cfg80211: validate channel settings across interfaces

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 <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/wireless/ibss.c b/net/wireless/ibss.c
index 4d7a084..42840a0 100644
--- a/net/wireless/ibss.c
+++ b/net/wireless/ibss.c
@@ -78,10 +78,15 @@
 			 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;
 
@@ -112,9 +117,11 @@
 	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;
 }
@@ -264,27 +271,32 @@
 
 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;
@@ -292,8 +304,7 @@
 	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)
@@ -307,9 +318,11 @@
 		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;
 }
@@ -347,6 +360,7 @@
 				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;
 
@@ -354,14 +368,13 @@
 	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)
@@ -375,9 +388,11 @@
 	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;
 }
@@ -414,6 +429,7 @@
 			     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;
 
@@ -421,7 +437,7 @@
 	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)
@@ -443,8 +459,7 @@
 	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)
@@ -456,9 +471,11 @@
 	} 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;
 }