hostap/wpa_supplicant/mbo.c
Vamsi Krishna 2e06cef80a MBO/OCE: Work around misbehaving MBO/OCE APs that use RSN without PMF
The MBO and OCE specification require the station to mandate use of PMF
when connecting to an MBO/OCE AP that uses WPA2. The earlier
implementation prevented such misbehaving APs from being selected for
connection completely. This looks like the safest approach to take, but
unfortunately, there are deployed APs that are not compliant with the
MBO/OCE requirements and this strict interpretation of the station
requirements results in interoperability issues by preventing the
association completely.

Relax the approach by allowing noncompliant MBO/OCE APs to be selected
for RSN connection without PMF to avoid the main impact of this
interoperability issue. However, disable MBO/OCE functionality when PMF
cannot be negotiated to try to be as compliant as practical with the
MBO/OCE tech spec requirements (i.e., stop being an MBO/OCE STA for the
duration of such workaround association). Also disable support for BTM
in this workaround state since MBO would expect all BTM frames to be
protected.

Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
2019-09-21 00:58:33 +03:00

665 lines
16 KiB
C

/*
* wpa_supplicant - MBO
*
* Copyright(c) 2015 Intel Deutschland GmbH
* Contact Information:
* Intel Linux Wireless <ilw@linux.intel.com>
* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "utils/includes.h"
#include "utils/common.h"
#include "common/ieee802_11_defs.h"
#include "common/gas.h"
#include "rsn_supp/wpa.h"
#include "config.h"
#include "wpa_supplicant_i.h"
#include "driver_i.h"
#include "bss.h"
#include "scan.h"
/* type + length + oui + oui type */
#define MBO_IE_HEADER 6
static int wpas_mbo_validate_non_pref_chan(u8 oper_class, u8 chan, u8 reason)
{
if (reason > MBO_NON_PREF_CHAN_REASON_INT_INTERFERENCE)
return -1;
/* Only checking the validity of the channel and oper_class */
if (ieee80211_chan_to_freq(NULL, oper_class, chan) == -1)
return -1;
return 0;
}
const u8 * mbo_attr_from_mbo_ie(const u8 *mbo_ie, enum mbo_attr_id attr)
{
const u8 *mbo;
u8 ie_len = mbo_ie[1];
if (ie_len < MBO_IE_HEADER - 2)
return NULL;
mbo = mbo_ie + MBO_IE_HEADER;
return get_ie(mbo, 2 + ie_len - MBO_IE_HEADER, attr);
}
const u8 * mbo_get_attr_from_ies(const u8 *ies, size_t ies_len,
enum mbo_attr_id attr)
{
const u8 *mbo_ie;
mbo_ie = get_vendor_ie(ies, ies_len, MBO_IE_VENDOR_TYPE);
if (!mbo_ie)
return NULL;
return mbo_attr_from_mbo_ie(mbo_ie, attr);
}
const u8 * wpas_mbo_get_bss_attr(struct wpa_bss *bss, enum mbo_attr_id attr)
{
const u8 *mbo, *end;
if (!bss)
return NULL;
mbo = wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE);
if (!mbo)
return NULL;
end = mbo + 2 + mbo[1];
mbo += MBO_IE_HEADER;
return get_ie(mbo, end - mbo, attr);
}
void wpas_mbo_check_pmf(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
struct wpa_ssid *ssid)
{
const u8 *rsne, *mbo, *oce;
struct wpa_ie_data ie;
wpa_s->disable_mbo_oce = 0;
if (!bss)
return;
mbo = wpas_mbo_get_bss_attr(bss, MBO_ATTR_ID_AP_CAPA_IND);
oce = wpas_mbo_get_bss_attr(bss, OCE_ATTR_ID_CAPA_IND);
if (!mbo && !oce)
return;
if (oce && oce[1] >= 1 && (oce[2] & OCE_IS_STA_CFON))
return; /* STA-CFON is not required to enable PMF */
rsne = wpa_bss_get_ie(bss, WLAN_EID_RSN);
if (!rsne || wpa_parse_wpa_ie(rsne, 2 + rsne[1], &ie) < 0)
return; /* AP is not using RSN */
if (!(ie.capabilities & WPA_CAPABILITY_MFPC))
wpa_s->disable_mbo_oce = 1; /* AP uses RSN without PMF */
if (wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION)
wpa_s->disable_mbo_oce = 1; /* STA uses RSN without PMF */
if (wpa_s->disable_mbo_oce)
wpa_printf(MSG_INFO,
"MBO: Disable MBO/OCE due to misbehaving AP not having enabled PMF");
}
static void wpas_mbo_non_pref_chan_attr_body(struct wpa_supplicant *wpa_s,
struct wpabuf *mbo,
u8 start, u8 end)
{
u8 i;
wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].oper_class);
for (i = start; i < end; i++)
wpabuf_put_u8(mbo, wpa_s->non_pref_chan[i].chan);
wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].preference);
wpabuf_put_u8(mbo, wpa_s->non_pref_chan[start].reason);
}
static void wpas_mbo_non_pref_chan_attr_hdr(struct wpabuf *mbo, size_t size)
{
wpabuf_put_u8(mbo, MBO_ATTR_ID_NON_PREF_CHAN_REPORT);
wpabuf_put_u8(mbo, size); /* Length */
}
static void wpas_mbo_non_pref_chan_attr(struct wpa_supplicant *wpa_s,
struct wpabuf *mbo, u8 start, u8 end)
{
size_t size = end - start + 3;
if (size + 2 > wpabuf_tailroom(mbo))
return;
wpas_mbo_non_pref_chan_attr_hdr(mbo, size);
wpas_mbo_non_pref_chan_attr_body(wpa_s, mbo, start, end);
}
static void wpas_mbo_non_pref_chan_subelem_hdr(struct wpabuf *mbo, u8 len)
{
wpabuf_put_u8(mbo, WLAN_EID_VENDOR_SPECIFIC);
wpabuf_put_u8(mbo, len); /* Length */
wpabuf_put_be24(mbo, OUI_WFA);
wpabuf_put_u8(mbo, MBO_ATTR_ID_NON_PREF_CHAN_REPORT);
}
static void wpas_mbo_non_pref_chan_subelement(struct wpa_supplicant *wpa_s,
struct wpabuf *mbo, u8 start,
u8 end)
{
size_t size = end - start + 7;
if (size + 2 > wpabuf_tailroom(mbo))
return;
wpas_mbo_non_pref_chan_subelem_hdr(mbo, size);
wpas_mbo_non_pref_chan_attr_body(wpa_s, mbo, start, end);
}
static void wpas_mbo_non_pref_chan_attrs(struct wpa_supplicant *wpa_s,
struct wpabuf *mbo, int subelement)
{
u8 i, start = 0;
struct wpa_mbo_non_pref_channel *start_pref;
if (!wpa_s->non_pref_chan || !wpa_s->non_pref_chan_num) {
if (subelement)
wpas_mbo_non_pref_chan_subelem_hdr(mbo, 4);
else
wpas_mbo_non_pref_chan_attr_hdr(mbo, 0);
return;
}
start_pref = &wpa_s->non_pref_chan[0];
for (i = 1; i <= wpa_s->non_pref_chan_num; i++) {
struct wpa_mbo_non_pref_channel *non_pref = NULL;
if (i < wpa_s->non_pref_chan_num)
non_pref = &wpa_s->non_pref_chan[i];
if (!non_pref ||
non_pref->oper_class != start_pref->oper_class ||
non_pref->reason != start_pref->reason ||
non_pref->preference != start_pref->preference) {
if (subelement)
wpas_mbo_non_pref_chan_subelement(wpa_s, mbo,
start, i);
else
wpas_mbo_non_pref_chan_attr(wpa_s, mbo, start,
i);
if (!non_pref)
return;
start = i;
start_pref = non_pref;
}
}
}
int wpas_mbo_ie(struct wpa_supplicant *wpa_s, u8 *buf, size_t len,
int add_oce_capa)
{
struct wpabuf *mbo;
int res;
if (len < MBO_IE_HEADER + 3 + 7 +
((wpa_s->enable_oce & OCE_STA) ? 3 : 0))
return 0;
/* Leave room for the MBO IE header */
mbo = wpabuf_alloc(len - MBO_IE_HEADER);
if (!mbo)
return 0;
/* Add non-preferred channels attribute */
wpas_mbo_non_pref_chan_attrs(wpa_s, mbo, 0);
/*
* Send cellular capabilities attribute even if AP does not advertise
* cellular capabilities.
*/
wpabuf_put_u8(mbo, MBO_ATTR_ID_CELL_DATA_CAPA);
wpabuf_put_u8(mbo, 1);
wpabuf_put_u8(mbo, wpa_s->conf->mbo_cell_capa);
/* Add OCE capability indication attribute if OCE is enabled */
if ((wpa_s->enable_oce & OCE_STA) && add_oce_capa) {
wpabuf_put_u8(mbo, OCE_ATTR_ID_CAPA_IND);
wpabuf_put_u8(mbo, 1);
wpabuf_put_u8(mbo, OCE_RELEASE);
}
res = mbo_add_ie(buf, len, wpabuf_head_u8(mbo), wpabuf_len(mbo));
if (!res)
wpa_printf(MSG_ERROR, "Failed to add MBO/OCE IE");
wpabuf_free(mbo);
return res;
}
static void wpas_mbo_send_wnm_notification(struct wpa_supplicant *wpa_s,
const u8 *data, size_t len)
{
struct wpabuf *buf;
int res;
/*
* Send WNM-Notification Request frame only in case of a change in
* non-preferred channels list during association, if the AP supports
* MBO.
*/
if (wpa_s->wpa_state != WPA_COMPLETED || !wpa_s->current_bss ||
!wpa_bss_get_vendor_ie(wpa_s->current_bss, MBO_IE_VENDOR_TYPE))
return;
buf = wpabuf_alloc(4 + len);
if (!buf)
return;
wpabuf_put_u8(buf, WLAN_ACTION_WNM);
wpabuf_put_u8(buf, WNM_NOTIFICATION_REQ);
wpa_s->mbo_wnm_token++;
if (wpa_s->mbo_wnm_token == 0)
wpa_s->mbo_wnm_token++;
wpabuf_put_u8(buf, wpa_s->mbo_wnm_token);
wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC); /* Type */
wpabuf_put_data(buf, data, len);
res = wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(buf), wpabuf_len(buf), 0);
if (res < 0)
wpa_printf(MSG_DEBUG,
"Failed to send WNM-Notification Request frame with non-preferred channel list");
wpabuf_free(buf);
}
static void wpas_mbo_non_pref_chan_changed(struct wpa_supplicant *wpa_s)
{
struct wpabuf *buf;
buf = wpabuf_alloc(512);
if (!buf)
return;
wpas_mbo_non_pref_chan_attrs(wpa_s, buf, 1);
wpas_mbo_send_wnm_notification(wpa_s, wpabuf_head_u8(buf),
wpabuf_len(buf));
wpas_update_mbo_connect_params(wpa_s);
wpabuf_free(buf);
}
static int wpa_non_pref_chan_is_eq(struct wpa_mbo_non_pref_channel *a,
struct wpa_mbo_non_pref_channel *b)
{
return a->oper_class == b->oper_class && a->chan == b->chan;
}
/*
* wpa_non_pref_chan_cmp - Compare two channels for sorting
*
* In MBO IE non-preferred channel subelement we can put many channels in an
* attribute if they are in the same operating class and have the same
* preference and reason. To make it easy for the functions that build
* the IE attributes and WNM Request subelements, save the channels sorted
* by their oper_class and reason.
*/
static int wpa_non_pref_chan_cmp(const void *_a, const void *_b)
{
const struct wpa_mbo_non_pref_channel *a = _a, *b = _b;
if (a->oper_class != b->oper_class)
return (int) a->oper_class - (int) b->oper_class;
if (a->reason != b->reason)
return (int) a->reason - (int) b->reason;
return (int) a->preference - (int) b->preference;
}
int wpas_mbo_update_non_pref_chan(struct wpa_supplicant *wpa_s,
const char *non_pref_chan)
{
char *cmd, *token, *context = NULL;
struct wpa_mbo_non_pref_channel *chans = NULL, *tmp_chans;
size_t num = 0, size = 0;
unsigned i;
wpa_printf(MSG_DEBUG, "MBO: Update non-preferred channels, non_pref_chan=%s",
non_pref_chan ? non_pref_chan : "N/A");
/*
* The shortest channel configuration is 7 characters - 3 colons and
* 4 values.
*/
if (!non_pref_chan || os_strlen(non_pref_chan) < 7)
goto update;
cmd = os_strdup(non_pref_chan);
if (!cmd)
return -1;
while ((token = str_token(cmd, " ", &context))) {
struct wpa_mbo_non_pref_channel *chan;
int ret;
unsigned int _oper_class;
unsigned int _chan;
unsigned int _preference;
unsigned int _reason;
if (num == size) {
size = size ? size * 2 : 1;
tmp_chans = os_realloc_array(chans, size,
sizeof(*chans));
if (!tmp_chans) {
wpa_printf(MSG_ERROR,
"Couldn't reallocate non_pref_chan");
goto fail;
}
chans = tmp_chans;
}
chan = &chans[num];
ret = sscanf(token, "%u:%u:%u:%u", &_oper_class,
&_chan, &_preference, &_reason);
if (ret != 4 ||
_oper_class > 255 || _chan > 255 ||
_preference > 255 || _reason > 65535 ) {
wpa_printf(MSG_ERROR, "Invalid non-pref chan input %s",
token);
goto fail;
}
chan->oper_class = _oper_class;
chan->chan = _chan;
chan->preference = _preference;
chan->reason = _reason;
if (wpas_mbo_validate_non_pref_chan(chan->oper_class,
chan->chan, chan->reason)) {
wpa_printf(MSG_ERROR,
"Invalid non_pref_chan: oper class %d chan %d reason %d",
chan->oper_class, chan->chan, chan->reason);
goto fail;
}
for (i = 0; i < num; i++)
if (wpa_non_pref_chan_is_eq(chan, &chans[i]))
break;
if (i != num) {
wpa_printf(MSG_ERROR,
"oper class %d chan %d is duplicated",
chan->oper_class, chan->chan);
goto fail;
}
num++;
}
os_free(cmd);
if (chans) {
qsort(chans, num, sizeof(struct wpa_mbo_non_pref_channel),
wpa_non_pref_chan_cmp);
}
update:
os_free(wpa_s->non_pref_chan);
wpa_s->non_pref_chan = chans;
wpa_s->non_pref_chan_num = num;
wpas_mbo_non_pref_chan_changed(wpa_s);
return 0;
fail:
os_free(chans);
os_free(cmd);
return -1;
}
void wpas_mbo_scan_ie(struct wpa_supplicant *wpa_s, struct wpabuf *ie)
{
u8 *len;
wpabuf_put_u8(ie, WLAN_EID_VENDOR_SPECIFIC);
len = wpabuf_put(ie, 1);
wpabuf_put_be24(ie, OUI_WFA);
wpabuf_put_u8(ie, MBO_OUI_TYPE);
wpabuf_put_u8(ie, MBO_ATTR_ID_CELL_DATA_CAPA);
wpabuf_put_u8(ie, 1);
wpabuf_put_u8(ie, wpa_s->conf->mbo_cell_capa);
if (wpa_s->enable_oce & OCE_STA) {
wpabuf_put_u8(ie, OCE_ATTR_ID_CAPA_IND);
wpabuf_put_u8(ie, 1);
wpabuf_put_u8(ie, OCE_RELEASE);
}
*len = (u8 *) wpabuf_put(ie, 0) - len - 1;
}
void wpas_mbo_ie_trans_req(struct wpa_supplicant *wpa_s, const u8 *mbo_ie,
size_t len)
{
const u8 *pos, *cell_pref = NULL;
u8 id, elen;
u16 disallowed_sec = 0;
if (len <= 4 || WPA_GET_BE24(mbo_ie) != OUI_WFA ||
mbo_ie[3] != MBO_OUI_TYPE)
return;
pos = mbo_ie + 4;
len -= 4;
while (len >= 2) {
id = *pos++;
elen = *pos++;
len -= 2;
if (elen > len)
goto fail;
switch (id) {
case MBO_ATTR_ID_CELL_DATA_PREF:
if (elen != 1)
goto fail;
if (wpa_s->conf->mbo_cell_capa ==
MBO_CELL_CAPA_AVAILABLE)
cell_pref = pos;
else
wpa_printf(MSG_DEBUG,
"MBO: Station does not support Cellular data connection");
break;
case MBO_ATTR_ID_TRANSITION_REASON:
if (elen != 1)
goto fail;
wpa_s->wnm_mbo_trans_reason_present = 1;
wpa_s->wnm_mbo_transition_reason = *pos;
break;
case MBO_ATTR_ID_ASSOC_RETRY_DELAY:
if (elen != 2)
goto fail;
if (wpa_s->wnm_mode &
WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) {
wpa_printf(MSG_DEBUG,
"MBO: Unexpected association retry delay, BSS is terminating");
goto fail;
} else if (wpa_s->wnm_mode &
WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
disallowed_sec = WPA_GET_LE16(pos);
wpa_printf(MSG_DEBUG,
"MBO: Association retry delay: %u",
disallowed_sec);
} else {
wpa_printf(MSG_DEBUG,
"MBO: Association retry delay attribute not in disassoc imminent mode");
}
break;
case MBO_ATTR_ID_AP_CAPA_IND:
case MBO_ATTR_ID_NON_PREF_CHAN_REPORT:
case MBO_ATTR_ID_CELL_DATA_CAPA:
case MBO_ATTR_ID_ASSOC_DISALLOW:
case MBO_ATTR_ID_TRANSITION_REJECT_REASON:
wpa_printf(MSG_DEBUG,
"MBO: Attribute %d should not be included in BTM Request frame",
id);
break;
default:
wpa_printf(MSG_DEBUG, "MBO: Unknown attribute id %u",
id);
return;
}
pos += elen;
len -= elen;
}
if (cell_pref)
wpa_msg(wpa_s, MSG_INFO, MBO_CELL_PREFERENCE "preference=%u",
*cell_pref);
if (wpa_s->wnm_mbo_trans_reason_present)
wpa_msg(wpa_s, MSG_INFO, MBO_TRANSITION_REASON "reason=%u",
wpa_s->wnm_mbo_transition_reason);
if (disallowed_sec && wpa_s->current_bss)
wpa_bss_tmp_disallow(wpa_s, wpa_s->current_bss->bssid,
disallowed_sec, 0);
return;
fail:
wpa_printf(MSG_DEBUG, "MBO IE parsing failed (id=%u len=%u left=%zu)",
id, elen, len);
}
size_t wpas_mbo_ie_bss_trans_reject(struct wpa_supplicant *wpa_s, u8 *pos,
size_t len,
enum mbo_transition_reject_reason reason)
{
u8 reject_attr[3];
reject_attr[0] = MBO_ATTR_ID_TRANSITION_REJECT_REASON;
reject_attr[1] = 1;
reject_attr[2] = reason;
return mbo_add_ie(pos, len, reject_attr, sizeof(reject_attr));
}
void wpas_mbo_update_cell_capa(struct wpa_supplicant *wpa_s, u8 mbo_cell_capa)
{
u8 cell_capa[7];
if (wpa_s->conf->mbo_cell_capa == mbo_cell_capa) {
wpa_printf(MSG_DEBUG,
"MBO: Cellular capability already set to %u",
mbo_cell_capa);
return;
}
wpa_s->conf->mbo_cell_capa = mbo_cell_capa;
cell_capa[0] = WLAN_EID_VENDOR_SPECIFIC;
cell_capa[1] = 5; /* Length */
WPA_PUT_BE24(cell_capa + 2, OUI_WFA);
cell_capa[5] = MBO_ATTR_ID_CELL_DATA_CAPA;
cell_capa[6] = mbo_cell_capa;
wpas_mbo_send_wnm_notification(wpa_s, cell_capa, 7);
wpa_supplicant_set_default_scan_ies(wpa_s);
wpas_update_mbo_connect_params(wpa_s);
}
struct wpabuf * mbo_build_anqp_buf(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, u32 mbo_subtypes)
{
struct wpabuf *anqp_buf;
u8 *len_pos;
u8 i;
if (!wpa_bss_get_vendor_ie(bss, MBO_IE_VENDOR_TYPE)) {
wpa_printf(MSG_INFO, "MBO: " MACSTR
" does not support MBO - cannot request MBO ANQP elements from it",
MAC2STR(bss->bssid));
return NULL;
}
/* Allocate size for the maximum case - all MBO subtypes are set */
anqp_buf = wpabuf_alloc(9 + MAX_MBO_ANQP_SUBTYPE);
if (!anqp_buf)
return NULL;
len_pos = gas_anqp_add_element(anqp_buf, ANQP_VENDOR_SPECIFIC);
wpabuf_put_be24(anqp_buf, OUI_WFA);
wpabuf_put_u8(anqp_buf, MBO_ANQP_OUI_TYPE);
wpabuf_put_u8(anqp_buf, MBO_ANQP_SUBTYPE_QUERY_LIST);
/* The first valid MBO subtype is 1 */
for (i = 1; i <= MAX_MBO_ANQP_SUBTYPE; i++) {
if (mbo_subtypes & BIT(i))
wpabuf_put_u8(anqp_buf, i);
}
gas_anqp_set_element_len(anqp_buf, len_pos);
return anqp_buf;
}
void mbo_parse_rx_anqp_resp(struct wpa_supplicant *wpa_s,
struct wpa_bss *bss, const u8 *sa,
const u8 *data, size_t slen)
{
const u8 *pos = data;
u8 subtype;
if (slen < 1)
return;
subtype = *pos++;
slen--;
switch (subtype) {
case MBO_ANQP_SUBTYPE_CELL_CONN_PREF:
if (slen < 1)
break;
wpa_msg(wpa_s, MSG_INFO, RX_MBO_ANQP MACSTR
" cell_conn_pref=%u", MAC2STR(sa), *pos);
break;
default:
wpa_printf(MSG_DEBUG, "MBO: Unsupported ANQP subtype %u",
subtype);
break;
}
}