Commit 77ee7c89 authored by Johannes Berg's avatar Johannes Berg

cfg80211: comprehensively check station changes

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: default avatarJohannes Berg <johannes.berg@intel.com>
parent ff276691
...@@ -2990,13 +2990,15 @@ static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev, ...@@ -2990,13 +2990,15 @@ static int ath6kl_change_station(struct wiphy *wiphy, struct net_device *dev,
{ {
struct ath6kl *ar = ath6kl_priv(dev); struct ath6kl *ar = ath6kl_priv(dev);
struct ath6kl_vif *vif = netdev_priv(dev); struct ath6kl_vif *vif = netdev_priv(dev);
int err;
if (vif->nw_type != AP_NETWORK) if (vif->nw_type != AP_NETWORK)
return -EOPNOTSUPP; return -EOPNOTSUPP;
/* Use this only for authorizing/unauthorizing a station */ err = cfg80211_check_station_change(wiphy, params,
if (!(params->sta_flags_mask & BIT(NL80211_STA_FLAG_AUTHORIZED))) CFG80211_STA_AP_MLME_CLIENT);
return -EOPNOTSUPP; if (err)
return err;
if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED)) if (params->sta_flags_set & BIT(NL80211_STA_FLAG_AUTHORIZED))
return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx, return ath6kl_wmi_ap_set_mlme(ar->wmi, vif->fw_vif_idx,
......
...@@ -677,6 +677,49 @@ struct station_parameters { ...@@ -677,6 +677,49 @@ struct station_parameters {
u8 ext_capab_len; u8 ext_capab_len;
}; };
/**
* 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 * enum station_info_flags - station information flags
* *
...@@ -1770,9 +1813,8 @@ struct cfg80211_gtk_rekey_data { ...@@ -1770,9 +1813,8 @@ struct cfg80211_gtk_rekey_data {
* @change_station: Modify a given station. Note that flags changes are not much * @change_station: Modify a given station. Note that flags changes are not much
* validated in cfg80211, in particular the auth/assoc/authorized flags * validated in cfg80211, in particular the auth/assoc/authorized flags
* might come to the driver in invalid combinations -- make sure to check * might come to the driver in invalid combinations -- make sure to check
* them, also against the existing state! Also, supported_rates changes are * them, also against the existing state! Drivers must call
* not checked in station mode -- drivers need to reject (or ignore) them * cfg80211_check_station_change() to validate the information.
* for anything but TDLS peers.
* @get_station: get station information for the station identified by @mac * @get_station: get station information for the station identified by @mac
* @dump_station: dump station callback -- resume dump at index @idx * @dump_station: dump station callback -- resume dump at index @idx
* *
......
...@@ -36,7 +36,21 @@ ...@@ -36,7 +36,21 @@
* The station is still assumed to belong to the AP interface it was added * The station is still assumed to belong to the AP interface it was added
* to. * 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
*/ */
/** /**
......
...@@ -1177,6 +1177,18 @@ static int sta_apply_parameters(struct ieee80211_local *local, ...@@ -1177,6 +1177,18 @@ static int sta_apply_parameters(struct ieee80211_local *local,
mask |= BIT(NL80211_STA_FLAG_ASSOCIATED); mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED)) if (set & BIT(NL80211_STA_FLAG_AUTHENTICATED))
set |= BIT(NL80211_STA_FLAG_ASSOCIATED); 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); ret = sta_apply_auth_flags(local, sta, mask, set);
...@@ -1261,9 +1273,8 @@ static int sta_apply_parameters(struct ieee80211_local *local, ...@@ -1261,9 +1273,8 @@ static int sta_apply_parameters(struct ieee80211_local *local,
if (ieee80211_vif_is_mesh(&sdata->vif)) { if (ieee80211_vif_is_mesh(&sdata->vif)) {
#ifdef CONFIG_MAC80211_MESH #ifdef CONFIG_MAC80211_MESH
u32 changed = 0; u32 changed = 0;
if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED &&
(params->sta_modify_mask & if (params->sta_modify_mask & STATION_PARAM_APPLY_PLINK_STATE) {
STATION_PARAM_APPLY_PLINK_STATE)) {
switch (params->plink_state) { switch (params->plink_state) {
case NL80211_PLINK_ESTAB: case NL80211_PLINK_ESTAB:
if (sta->plink_state != NL80211_PLINK_ESTAB) if (sta->plink_state != NL80211_PLINK_ESTAB)
...@@ -1294,21 +1305,18 @@ static int sta_apply_parameters(struct ieee80211_local *local, ...@@ -1294,21 +1305,18 @@ static int sta_apply_parameters(struct ieee80211_local *local,
/* nothing */ /* nothing */
break; break;
} }
} else if (params->sta_modify_mask & }
STATION_PARAM_APPLY_PLINK_STATE) {
return -EINVAL; switch (params->plink_action) {
} else { case NL80211_PLINK_ACTION_NO_ACTION:
switch (params->plink_action) { /* nothing */
case NL80211_PLINK_ACTION_NO_ACTION: break;
/* nothing */ case NL80211_PLINK_ACTION_OPEN:
break; changed |= mesh_plink_open(sta);
case NL80211_PLINK_ACTION_OPEN: break;
changed |= mesh_plink_open(sta); case NL80211_PLINK_ACTION_BLOCK:
break; changed |= mesh_plink_block(sta);
case NL80211_PLINK_ACTION_BLOCK: break;
changed |= mesh_plink_block(sta);
break;
}
} }
if (params->local_pm) if (params->local_pm)
...@@ -1354,8 +1362,10 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, ...@@ -1354,8 +1362,10 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev,
* defaults -- if userspace wants something else we'll * defaults -- if userspace wants something else we'll
* change it accordingly in sta_apply_parameters() * change it accordingly in sta_apply_parameters()
*/ */
sta_info_pre_move_state(sta, IEEE80211_STA_AUTH); if (!(params->sta_flags_set & BIT(NL80211_STA_FLAG_TDLS_PEER))) {
sta_info_pre_move_state(sta, IEEE80211_STA_ASSOC); 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); err = sta_apply_parameters(local, sta, params);
if (err) { if (err) {
...@@ -1364,8 +1374,8 @@ static int ieee80211_add_station(struct wiphy *wiphy, struct net_device *dev, ...@@ -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 * for TDLS, rate control should be initialized only when
* rates are known. * rates are known and station is marked authorized
*/ */
if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER)) if (!test_sta_flag(sta, WLAN_STA_TDLS_PEER))
rate_control_rate_init(sta); rate_control_rate_init(sta);
...@@ -1402,50 +1412,67 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev, ...@@ -1402,50 +1412,67 @@ static int ieee80211_del_station(struct wiphy *wiphy, struct net_device *dev,
} }
static int ieee80211_change_station(struct wiphy *wiphy, static int ieee80211_change_station(struct wiphy *wiphy,
struct net_device *dev, struct net_device *dev, u8 *mac,
u8 *mac,
struct station_parameters *params) struct station_parameters *params)
{ {
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = wiphy_priv(wiphy); struct ieee80211_local *local = wiphy_priv(wiphy);
struct sta_info *sta; struct sta_info *sta;
struct ieee80211_sub_if_data *vlansdata; struct ieee80211_sub_if_data *vlansdata;
enum cfg80211_station_type statype;
int err; int err;
mutex_lock(&local->sta_mtx); mutex_lock(&local->sta_mtx);
sta = sta_info_get_bss(sdata, mac); sta = sta_info_get_bss(sdata, mac);
if (!sta) { if (!sta) {
mutex_unlock(&local->sta_mtx); err = -ENOENT;
return -ENOENT; goto out_err;
} }
/* in station mode, some updates are only valid with TDLS */ switch (sdata->vif.type) {
if (sdata->vif.type == NL80211_IFTYPE_STATION && case NL80211_IFTYPE_MESH_POINT:
(params->supported_rates || params->ht_capa || params->vht_capa || if (sdata->u.mesh.security & IEEE80211_MESH_SEC_SECURED)
params->sta_modify_mask || statype = CFG80211_STA_MESH_PEER_SECURE;
(params->sta_flags_mask & BIT(NL80211_STA_FLAG_WME))) && else
!test_sta_flag(sta, WLAN_STA_TDLS_PEER)) { statype = CFG80211_STA_MESH_PEER_NONSEC;
mutex_unlock(&local->sta_mtx); break;
return -EINVAL; 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) { if (params->vlan && params->vlan != sta->sdata->dev) {
bool prev_4addr = false; bool prev_4addr = false;
bool new_4addr = false; bool new_4addr = false;
vlansdata = IEEE80211_DEV_TO_SUB_IF(params->vlan); 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 (params->vlan->ieee80211_ptr->use_4addr) {
if (vlansdata->u.vlan.sta) { if (vlansdata->u.vlan.sta) {
mutex_unlock(&local->sta_mtx); err = -EBUSY;
return -EBUSY; goto out_err;
} }
rcu_assign_pointer(vlansdata->u.vlan.sta, sta); rcu_assign_pointer(vlansdata->u.vlan.sta, sta);
...@@ -1472,12 +1499,12 @@ static int ieee80211_change_station(struct wiphy *wiphy, ...@@ -1472,12 +1499,12 @@ static int ieee80211_change_station(struct wiphy *wiphy,
} }
err = sta_apply_parameters(local, sta, params); err = sta_apply_parameters(local, sta, params);
if (err) { if (err)
mutex_unlock(&local->sta_mtx); goto out_err;
return 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); rate_control_rate_init(sta);
mutex_unlock(&local->sta_mtx); mutex_unlock(&local->sta_mtx);
...@@ -1487,7 +1514,11 @@ static int ieee80211_change_station(struct wiphy *wiphy, ...@@ -1487,7 +1514,11 @@ static int ieee80211_change_station(struct wiphy *wiphy,
ieee80211_recalc_ps(local, -1); ieee80211_recalc_ps(local, -1);
ieee80211_recalc_ps_vif(sdata); ieee80211_recalc_ps_vif(sdata);
} }
return 0; return 0;
out_err:
mutex_unlock(&local->sta_mtx);
return err;
} }
#ifdef CONFIG_MAC80211_MESH #ifdef CONFIG_MAC80211_MESH
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment