cfg80211: comprehensively check station changes
authorJohannes Berg <johannes.berg@intel.com>
Thu, 14 Feb 2013 23:48:33 +0000 (00:48 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 6 Mar 2013 15:35:40 +0000 (16:35 +0100)
The station change API isn't being checked properly before
drivers are called, and as a result it is difficult to see
what should be allowed and what not.

In order to comprehensively check the API parameters parse
everything first, and then have the driver call a function
(cfg80211_check_station_change()) with the additionally
information about the kind of station that is being changed;
this allows the function to make better decisions than the
old code could.

While at it, also add a few checks, particularly in mesh
and clarify the TDLS station lifetime in documentation.

To be able to reduce a few checks, ignore any flag set bits
when the mask isn't set, they shouldn't be applied then.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
drivers/net/wireless/ath/ath6kl/cfg80211.c
include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/mac80211/cfg.c
net/wireless/nl80211.c

index 752ffc4..28c413f 100644 (file)
@@ -2990,13 +2990,15 @@ static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev,
 {
        struct ath6kl *ar = ath6kl_priv(dev);
        struct ath6kl_vif *vif = netdev_priv(dev);
+       int err;
 
        if (vif->nw_type != AP_NETWORK)
                return -EOPNOTSUPP;
 
-       /* Use this only for authorizing/unauthorizing a station */
-       if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)))
-               return -EOPNOTSUPP;
+       err = cfg80211_check_station_change(wiphy, params,
+                                           CFG80211_STA_AP_MLME_CLIENT);
+       if (err)
+               return err;
 
        if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED))
                return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx,
index 7ca321d..ed2b08d 100644 (file)
@@ -678,6 +678,49 @@ struct station_parameters {
 };
 
 /**
+ * enum cfg80211_station_type - the type of station being modified
+ * @CFG80211_STA_AP_CLIENT: client of an AP interface
+ * @CFG80211_STA_AP_MLME_CLIENT: client of an AP interface that has
+ *     the AP MLME in the device
+ * @CFG80211_STA_AP_STA: AP station on managed interface
+ * @CFG80211_STA_IBSS: IBSS station
+ * @CFG80211_STA_TDLS_PEER_SETUP: TDLS peer on managed interface (dummy entry
+ *     while TDLS setup is in progress, it moves out of this state when
+ *     being marked authorized; use this only if TDLS with external setup is
+ *     supported/used)
+ * @CFG80211_STA_TDLS_PEER_ACTIVE: TDLS peer on managed interface (active
+ *     entry that is operating, has been marked authorized by userspace)
+ * @CFG80211_STA_MESH_PEER_NONSEC: peer on mesh interface (non-secured)
+ * @CFG80211_STA_MESH_PEER_SECURE: peer on mesh interface (secured)
+ */
+enum cfg80211_station_type {
+       CFG80211_STA_AP_CLIENT,
+       CFG80211_STA_AP_MLME_CLIENT,
+       CFG80211_STA_AP_STA,
+       CFG80211_STA_IBSS,
+       CFG80211_STA_TDLS_PEER_SETUP,
+       CFG80211_STA_TDLS_PEER_ACTIVE,
+       CFG80211_STA_MESH_PEER_NONSEC,
+       CFG80211_STA_MESH_PEER_SECURE,
+};
+
+/**
+ * cfg80211_check_station_change - validate parameter changes
+ * @wiphy: the wiphy this operates on
+ * @params: the new parameters for a station
+ * @statype: the type of station being modified
+ *
+ * Utility function for the @change_station driver method. Call this function
+ * with the appropriate station type looking up the station (and checking that
+ * it exists). It will verify whether the station change is acceptable, and if
+ * not will return an error code. Note that it may modify the parameters for
+ * backward compatibility reasons, so don't use them before calling this.
+ */
+int cfg80211_check_station_change(struct wiphy *wiphy,
+                                 struct station_parameters *params,
+                                 enum cfg80211_station_type statype);
+
+/**
  * enum station_info_flags - station information flags
  *
  * Used by the driver to indicate which info in &struct station_info
@@ -1770,9 +1813,8 @@ struct cfg80211_gtk_rekey_data {
  * @change_station: Modify a given station. Note that flags changes are not much
  *     validated in cfg80211, in particular the auth/assoc/authorized flags
  *     might come to the driver in invalid combinations -- make sure to check
- *     them, also against the existing state! Also, supported_rates changes are
- *     not checked in station mode -- drivers need to reject (or ignore) them
- *     for anything but TDLS peers.
+ *     them, also against the existing state! Drivers must call
+ *     cfg80211_check_station_change() to validate the information.
  * @get_station: get station information for the station identified by @mac
  * @dump_station: dump station callback -- resume dump at index @idx
  *
index 7dcc69f..523ed3d 100644 (file)
  * The station is still assumed to belong to the AP interface it was added
  * to.
  *
- * TODO: need more info?
+ * Station handling varies per interface type and depending on the driver's
+ * capabilities.
+ *
+ * For drivers supporting TDLS with external setup (WIPHY_FLAG_SUPPORTS_TDLS
+ * and WIPHY_FLAG_TDLS_EXTERNAL_SETUP), the station lifetime is as follows:
+ *  - a setup station entry is added, not yet authorized, without any rate
+ *    or capability information, this just exists to avoid race conditions
+ *  - when the TDLS setup is done, a single NL80211_CMD_SET_STATION is valid
+ *    to add rate and capability information to the station and at the same
+ *    time mark it authorized.
+ *  - %NL80211_TDLS_ENABLE_LINK is then used
+ *  - after this, the only valid operation is to remove it by tearing down
+ *    the TDLS link (%NL80211_TDLS_DISABLE_LINK)
+ *
+ * TODO: need more info for other interface types
  */
 
 /**
index ca28405..c115f82 100644 (file)
@@ -1177,6 +1177,18 @@ static int sta_apply_parameters(struct ieee80211_local *local,
                        mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
                if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
                        set |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+       } else if (test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
+               /*
+                * TDLS -- everything follows authorized, but
+                * only becoming authorized is possible, not
+                * going back
+                */
+               if (set & BIT(NL80211_STA_FLAG_AUTHORIZED)) {
+                       set |= BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                              BIT(NL80211_STA_FLAG_ASSOCIATED);
+                       mask |= BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                               BIT(NL80211_STA_FLAG_ASSOCIATED);
+               }
        }
 
        ret = sta_apply_auth_flags(local, sta, mask, set);
@@ -1261,9 +1273,8 @@ static int sta_apply_parameters(struct ieee80211_local *local,
        if (ieee80211_vif_is_mesh(&sdata->vif)) {
 #ifdef CONFIG_MAC80211_MESH
                u32 changed = 0;
-               if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED &&
-                   (params->sta_modify_mask &
-                                       STATION_PARAM_APPLY_PLINK_STATE)) {
+
+               if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) {
                        switch (params->plink_state) {
                        case NL80211_PLINK_ESTAB:
                                if (sta->plink_state != NL80211_PLINK_ESTAB)
@@ -1294,21 +1305,18 @@ static int sta_apply_parameters(struct ieee80211_local *local,
                                /*  nothing  */
                                break;
                        }
-               } else if (params->sta_modify_mask &
-                                       STATION_PARAM_APPLY_PLINK_STATE) {
-                       return -EINVAL;
-               } else {
-                       switch (params->plink_action) {
-                       case NL80211_PLINK_ACTION_NO_ACTION:
-                               /* nothing */
-                               break;
-                       case NL80211_PLINK_ACTION_OPEN:
-                               changed |= mesh_plink_open(sta);
-                               break;
-                       case NL80211_PLINK_ACTION_BLOCK:
-                               changed |= mesh_plink_block(sta);
-                               break;
-                       }
+               }
+
+               switch (params->plink_action) {
+               case NL80211_PLINK_ACTION_NO_ACTION:
+                       /* nothing */
+                       break;
+               case NL80211_PLINK_ACTION_OPEN:
+                       changed |= mesh_plink_open(sta);
+                       break;
+               case NL80211_PLINK_ACTION_BLOCK:
+                       changed |= mesh_plink_block(sta);
+                       break;
                }
 
                if (params->local_pm)
@@ -1354,8 +1362,10 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
         * defaults -- if userspace wants something else we'll
         * change it accordingly in sta_apply_parameters()
         */
-       sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
-       sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
+       if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))) {
+               sta_info_pre_move_state(sta, IEEE80211_STA_AUTH);
+               sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC);
+       }
 
        err = sta_apply_parameters(local, sta, params);
        if (err) {
@@ -1364,8 +1374,8 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
        }
 
        /*
-        * for TDLS, rate control should be initialized only when supported
-        * rates are known.
+        * for TDLS, rate control should be initialized only when
+        * rates are known and station is marked authorized
         */
        if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER))
                rate_control_rate_init(sta);
@@ -1402,50 +1412,67 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
 }
 
 static int ieee80211_change_station(struct wiphy *wiphy,
-                                   struct net_device *dev,
-                                   u8 *mac,
+                                   struct net_device *dev, u8 *mac,
                                    struct station_parameters *params)
 {
        struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
        struct ieee80211_local *local = wiphy_priv(wiphy);
        struct sta_info *sta;
        struct ieee80211_sub_if_data *vlansdata;
+       enum cfg80211_station_type statype;
        int err;
 
        mutex_lock(&local->sta_mtx);
 
        sta = sta_info_get_bss(sdata, mac);
        if (!sta) {
-               mutex_unlock(&local->sta_mtx);
-               return -ENOENT;
+               err = -ENOENT;
+               goto out_err;
        }
 
-       /* in station mode, some updates are only valid with TDLS */
-       if (sdata->vif.type == NL80211_IFTYPE_STATION &&
-           (params->supported_rates || params->ht_capa || params->vht_capa ||
-            params->sta_modify_mask ||
-            (params->sta_flags_mask & BIT(NL80211_STA_FLAG_WME))) &&
-           !test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
-               mutex_unlock(&local->sta_mtx);
-               return -EINVAL;
+       switch (sdata->vif.type) {
+       case NL80211_IFTYPE_MESH_POINT:
+               if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED)
+                       statype = CFG80211_STA_MESH_PEER_SECURE;
+               else
+                       statype = CFG80211_STA_MESH_PEER_NONSEC;
+               break;
+       case NL80211_IFTYPE_ADHOC:
+               statype = CFG80211_STA_IBSS;
+               break;
+       case NL80211_IFTYPE_STATION:
+               if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER)) {
+                       statype = CFG80211_STA_AP_STA;
+                       break;
+               }
+               if (test_sta_flag(sta, WLAN_STA_AUTHORIZED))
+                       statype = CFG80211_STA_TDLS_PEER_ACTIVE;
+               else
+                       statype = CFG80211_STA_TDLS_PEER_SETUP;
+               break;
+       case NL80211_IFTYPE_AP:
+       case NL80211_IFTYPE_AP_VLAN:
+               statype = CFG80211_STA_AP_CLIENT;
+               break;
+       default:
+               err = -EOPNOTSUPP;
+               goto out_err;
        }
 
+       err = cfg80211_check_station_change(wiphy, params, statype);
+       if (err)
+               goto out_err;
+
        if (params->vlan && params->vlan != sta->sdata->dev) {
                bool prev_4addr = false;
                bool new_4addr = false;
 
                vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan);
 
-               if (vlansdata->vif.type != NL80211_IFTYPE_AP_VLAN &&
-                   vlansdata->vif.type != NL80211_IFTYPE_AP) {
-                       mutex_unlock(&local->sta_mtx);
-                       return -EINVAL;
-               }
-
                if (params->vlan->ieee80211_ptr->use_4addr) {
                        if (vlansdata->u.vlan.sta) {
-                               mutex_unlock(&local->sta_mtx);
-                               return -EBUSY;
+                               err = -EBUSY;
+                               goto out_err;
                        }
 
                        rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
@@ -1472,12 +1499,12 @@ static int ieee80211_change_station(struct wiphy *wiphy,
        }
 
        err = sta_apply_parameters(local, sta, params);
-       if (err) {
-               mutex_unlock(&local->sta_mtx);
-               return err;
-       }
+       if (err)
+               goto out_err;
 
-       if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) && params->supported_rates)
+       /* When peer becomes authorized, init rate control as well */
+       if (test_sta_flag(sta, WLAN_STA_TDLS_PEER) &&
+           test_sta_flag(sta, WLAN_STA_AUTHORIZED))
                rate_control_rate_init(sta);
 
        mutex_unlock(&local->sta_mtx);
@@ -1487,7 +1514,11 @@ static int ieee80211_change_station(struct wiphy *wiphy,
                ieee80211_recalc_ps(local, -1);
                ieee80211_recalc_ps_vif(sdata);
        }
+
        return 0;
+out_err:
+       mutex_unlock(&local->sta_mtx);
+       return err;
 }
 
 #ifdef CONFIG_MAC80211_MESH
index 9e7c104..83151a5 100644 (file)
@@ -2967,6 +2967,7 @@ static int parse_station_flags(struct genl_info *info,
                sta_flags = nla_data(nla);
                params->sta_flags_mask = sta_flags->mask;
                params->sta_flags_set = sta_flags->set;
+               params->sta_flags_set &= params->sta_flags_mask;
                if ((params->sta_flags_mask |
                     params->sta_flags_set) & BIT(__NL80211_STA_FLAG_INVALID))
                        return -EINVAL;
@@ -3320,6 +3321,136 @@ static int nl80211_get_station(struct sk_buff *skb, struct genl_info *info)
        return genlmsg_reply(msg, info);
 }
 
+int cfg80211_check_station_change(struct wiphy *wiphy,
+                                 struct station_parameters *params,
+                                 enum cfg80211_station_type statype)
+{
+       if (params->listen_interval != -1)
+               return -EINVAL;
+       if (params->aid)
+               return -EINVAL;
+
+       /* When you run into this, adjust the code below for the new flag */
+       BUILD_BUG_ON(NL80211_STA_FLAG_MAX != 7);
+
+       switch (statype) {
+       case CFG80211_STA_MESH_PEER_NONSEC:
+       case CFG80211_STA_MESH_PEER_SECURE:
+               /*
+                * No ignoring the TDLS flag here -- the userspace mesh
+                * code doesn't have the bug of including TDLS in the
+                * mask everywhere.
+                */
+               if (params->sta_flags_mask &
+                               ~(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                 BIT(NL80211_STA_FLAG_MFP) |
+                                 BIT(NL80211_STA_FLAG_AUTHORIZED)))
+                       return -EINVAL;
+               break;
+       case CFG80211_STA_TDLS_PEER_SETUP:
+       case CFG80211_STA_TDLS_PEER_ACTIVE:
+               if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
+                       return -EINVAL;
+               /* ignore since it can't change */
+               params->sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
+               break;
+       default:
+               /* disallow mesh-specific things */
+               if (params->plink_action != NL80211_PLINK_ACTION_NO_ACTION)
+                       return -EINVAL;
+               if (params->local_pm)
+                       return -EINVAL;
+               if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE)
+                       return -EINVAL;
+       }
+
+       if (statype != CFG80211_STA_TDLS_PEER_SETUP &&
+           statype != CFG80211_STA_TDLS_PEER_ACTIVE) {
+               /* TDLS can't be set, ... */
+               if (params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
+                       return -EINVAL;
+               /*
+                * ... but don't bother the driver with it. This works around
+                * a hostapd/wpa_supplicant issue -- it always includes the
+                * TLDS_PEER flag in the mask even for AP mode.
+                */
+               params->sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
+       }
+
+       if (statype != CFG80211_STA_TDLS_PEER_SETUP) {
+               /* reject other things that can't change */
+               if (params->sta_modify_mask & STATION_PARAM_APPLY_UAPSD)
+                       return -EINVAL;
+               if (params->sta_modify_mask & STATION_PARAM_APPLY_CAPABILITY)
+                       return -EINVAL;
+               if (params->supported_rates)
+                       return -EINVAL;
+               if (params->ext_capab || params->ht_capa || params->vht_capa)
+                       return -EINVAL;
+       }
+
+       if (statype != CFG80211_STA_AP_CLIENT) {
+               if (params->vlan)
+                       return -EINVAL;
+       }
+
+       switch (statype) {
+       case CFG80211_STA_AP_MLME_CLIENT:
+               /* Use this only for authorizing/unauthorizing a station */
+               if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED)))
+                       return -EOPNOTSUPP;
+               break;
+       case CFG80211_STA_AP_CLIENT:
+               /* accept only the listed bits */
+               if (params->sta_flags_mask &
+                               ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+                                 BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                 BIT(NL80211_STA_FLAG_ASSOCIATED) |
+                                 BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) |
+                                 BIT(NL80211_STA_FLAG_WME) |
+                                 BIT(NL80211_STA_FLAG_MFP)))
+                       return -EINVAL;
+
+               /* but authenticated/associated only if driver handles it */
+               if (!(wiphy->features & NL80211_FEATURE_FULL_AP_CLIENT_STATE) &&
+                   params->sta_flags_mask &
+                               (BIT(NL80211_STA_FLAG_AUTHENTICATED) |
+                                BIT(NL80211_STA_FLAG_ASSOCIATED)))
+                       return -EINVAL;
+               break;
+       case CFG80211_STA_IBSS:
+       case CFG80211_STA_AP_STA:
+               /* reject any changes other than AUTHORIZED */
+               if (params->sta_flags_mask & ~BIT(NL80211_STA_FLAG_AUTHORIZED))
+                       return -EINVAL;
+               break;
+       case CFG80211_STA_TDLS_PEER_SETUP:
+               /* reject any changes other than AUTHORIZED or WME */
+               if (params->sta_flags_mask & ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
+                                              BIT(NL80211_STA_FLAG_WME)))
+                       return -EINVAL;
+               /* force (at least) rates when authorizing */
+               if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED) &&
+                   !params->supported_rates)
+                       return -EINVAL;
+               break;
+       case CFG80211_STA_TDLS_PEER_ACTIVE:
+               /* reject any changes */
+               return -EINVAL;
+       case CFG80211_STA_MESH_PEER_NONSEC:
+               if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE)
+                       return -EINVAL;
+               break;
+       case CFG80211_STA_MESH_PEER_SECURE:
+               if (params->plink_action != NL80211_PLINK_ACTION_NO_ACTION)
+                       return -EINVAL;
+               break;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL(cfg80211_check_station_change);
+
 /*
  * Get vlan interface making sure it is running and on the right wiphy.
  */
@@ -3342,6 +3473,13 @@ static struct net_device *get_vlan(struct genl_info *info,
                goto error;
        }
 
+       if (v->ieee80211_ptr->iftype != NL80211_IFTYPE_AP_VLAN &&
+           v->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+           v->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO) {
+               ret = -EINVAL;
+               goto error;
+       }
+
        if (!netif_running(v)) {
                ret = -ENETDOWN;
                goto error;
@@ -3410,15 +3548,18 @@ static int nl80211_set_station_tdls(struct genl_info *info,
 static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
-       int err;
        struct net_device *dev = info->user_ptr[1];
        struct station_parameters params;
-       u8 *mac_addr = NULL;
+       u8 *mac_addr;
+       int err;
 
        memset(&params, 0, sizeof(params));
 
        params.listen_interval = -1;
 
+       if (!rdev->ops->change_station)
+               return -EOPNOTSUPP;
+
        if (info->attrs[NL80211_ATTR_STA_AID])
                return -EINVAL;
 
@@ -3450,9 +3591,6 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
        if (info->attrs[NL80211_ATTR_STA_LISTEN_INTERVAL])
                return -EINVAL;
 
-       if (!rdev->ops->change_station)
-               return -EOPNOTSUPP;
-
        if (parse_station_flags(info, dev->ieee80211_ptr->iftype, &params))
                return -EINVAL;
 
@@ -3482,133 +3620,33 @@ static int nl80211_set_station(struct sk_buff *skb, struct genl_info *info)
                params.local_pm = pm;
        }
 
+       /* Include parameters for TDLS peer (will check later) */
+       err = nl80211_set_station_tdls(info, &params);
+       if (err)
+               return err;
+
+       params.vlan = get_vlan(info, rdev);
+       if (IS_ERR(params.vlan))
+               return PTR_ERR(params.vlan);
+
        switch (dev->ieee80211_ptr->iftype) {
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_AP_VLAN:
        case NL80211_IFTYPE_P2P_GO:
-               /* disallow mesh-specific things */
-               if (params.plink_action)
-                       return -EINVAL;
-               if (params.local_pm)
-                       return -EINVAL;
-               if (params.sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE)
-                       return -EINVAL;
-
-               /* TDLS can't be set, ... */
-               if (params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))
-                       return -EINVAL;
-               /*
-                * ... but don't bother the driver with it. This works around
-                * a hostapd/wpa_supplicant issue -- it always includes the
-                * TLDS_PEER flag in the mask even for AP mode.
-                */
-               params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
-
-               /* accept only the listed bits */
-               if (params.sta_flags_mask &
-                               ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
-                                 BIT(NL80211_STA_FLAG_AUTHENTICATED) |
-                                 BIT(NL80211_STA_FLAG_ASSOCIATED) |
-                                 BIT(NL80211_STA_FLAG_SHORT_PREAMBLE) |
-                                 BIT(NL80211_STA_FLAG_WME) |
-                                 BIT(NL80211_STA_FLAG_MFP)))
-                       return -EINVAL;
-
-               /* but authenticated/associated only if driver handles it */
-               if (!(rdev->wiphy.features &
-                               NL80211_FEATURE_FULL_AP_CLIENT_STATE) &&
-                   params.sta_flags_mask &
-                               (BIT(NL80211_STA_FLAG_AUTHENTICATED) |
-                                BIT(NL80211_STA_FLAG_ASSOCIATED)))
-                       return -EINVAL;
-
-               /* reject other things that can't change */
-               if (params.supported_rates)
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_STA_CAPABILITY])
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY])
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
-                   info->attrs[NL80211_ATTR_VHT_CAPABILITY])
-                       return -EINVAL;
-
-               /* must be last in here for error handling */
-               params.vlan = get_vlan(info, rdev);
-               if (IS_ERR(params.vlan))
-                       return PTR_ERR(params.vlan);
-               break;
        case NL80211_IFTYPE_P2P_CLIENT:
        case NL80211_IFTYPE_STATION:
-               /*
-                * Don't allow userspace to change the TDLS_PEER flag,
-                * but silently ignore attempts to change it since we
-                * don't have state here to verify that it doesn't try
-                * to change the flag.
-                */
-               params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_TDLS_PEER);
-               /* Include parameters for TDLS peer (driver will check) */
-               err = nl80211_set_station_tdls(info, &params);
-               if (err)
-                       return err;
-               /* disallow things sta doesn't support */
-               if (params.plink_action)
-                       return -EINVAL;
-               if (params.local_pm)
-                       return -EINVAL;
-               if (params.sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE)
-                       return -EINVAL;
-               /* reject any changes other than AUTHORIZED or WME (for TDLS) */
-               if (params.sta_flags_mask & ~(BIT(NL80211_STA_FLAG_AUTHORIZED) |
-                                             BIT(NL80211_STA_FLAG_WME)))
-                       return -EINVAL;
-               break;
        case NL80211_IFTYPE_ADHOC:
-               /* disallow things sta doesn't support */
-               if (params.plink_action)
-                       return -EINVAL;
-               if (params.local_pm)
-                       return -EINVAL;
-               if (params.sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE)
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
-                   info->attrs[NL80211_ATTR_VHT_CAPABILITY])
-                       return -EINVAL;
-               /* reject any changes other than AUTHORIZED */
-               if (params.sta_flags_mask & ~BIT(NL80211_STA_FLAG_AUTHORIZED))
-                       return -EINVAL;
-               break;
        case NL80211_IFTYPE_MESH_POINT:
-               /* disallow things mesh doesn't support */
-               if (params.vlan)
-                       return -EINVAL;
-               if (params.supported_rates)
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_STA_CAPABILITY])
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_STA_EXT_CAPABILITY])
-                       return -EINVAL;
-               if (info->attrs[NL80211_ATTR_HT_CAPABILITY] ||
-                   info->attrs[NL80211_ATTR_VHT_CAPABILITY])
-                       return -EINVAL;
-               /*
-                * No special handling for TDLS here -- the userspace
-                * mesh code doesn't have this bug.
-                */
-               if (params.sta_flags_mask &
-                               ~(BIT(NL80211_STA_FLAG_AUTHENTICATED) |
-                                 BIT(NL80211_STA_FLAG_MFP) |
-                                 BIT(NL80211_STA_FLAG_AUTHORIZED)))
-                       return -EINVAL;
                break;
        default:
-               return -EOPNOTSUPP;
+               err = -EOPNOTSUPP;
+               goto out_put_vlan;
        }
 
-       /* be aware of params.vlan when changing code here */
-
+       /* driver will call cfg80211_check_station_change() */
        err = rdev_change_station(rdev, dev, mac_addr, &params);
 
+ out_put_vlan:
        if (params.vlan)
                dev_put(params.vlan);
 
@@ -3687,6 +3725,9 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
        if (parse_station_flags(info, dev->ieee80211_ptr->iftype, &params))
                return -EINVAL;
 
+       /* When you run into this, adjust the code below for the new flag */
+       BUILD_BUG_ON(NL80211_STA_FLAG_MAX != 7);
+
        switch (dev->ieee80211_ptr->iftype) {
        case NL80211_IFTYPE_AP:
        case NL80211_IFTYPE_AP_VLAN:
@@ -3730,8 +3771,10 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
                /* ignore uAPSD data */
                params.sta_modify_mask &= ~STATION_PARAM_APPLY_UAPSD;
 
-               /* associated is disallowed */
-               if (params.sta_flags_mask & BIT(NL80211_STA_FLAG_ASSOCIATED))
+               /* these are disallowed */
+               if (params.sta_flags_mask &
+                               (BIT(NL80211_STA_FLAG_ASSOCIATED) |
+                                BIT(NL80211_STA_FLAG_AUTHENTICATED)))
                        return -EINVAL;
                /* Only TDLS peers can be added */
                if (!(params.sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER)))
@@ -3742,6 +3785,11 @@ static int nl80211_new_station(struct sk_buff *skb, struct genl_info *info)
                /* ... with external setup is supported */
                if (!(rdev->wiphy.flags & WIPHY_FLAG_TDLS_EXTERNAL_SETUP))
                        return -EOPNOTSUPP;
+               /*
+                * Older wpa_supplicant versions always mark the TDLS peer
+                * as authorized, but it shouldn't yet be.
+                */
+               params.sta_flags_mask &= ~BIT(NL80211_STA_FLAG_AUTHORIZED);
                break;
        default:
                return -EOPNOTSUPP;