mirror of
https://github.com/vanhoefm/fragattacks.git
synced 2024-11-25 00:38:24 -05:00
b3c43c3c24
If a sniffer capture does not include FCS for each frame, but may included frames with invalid FCS, it would be possible for wlantest to try to decrypt the first received frame and fail (e.g., due to CCMP MIC mismatch) because that particular frame was corrupted and then ignore the following retry of that frame as a duplicate even if that retry has different payload (e.g., if its reception did not show corruption). Work around this by skipping duplicate frame detection immediately following a decryption failure. Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
1548 lines
41 KiB
C
1548 lines
41 KiB
C
/*
|
|
* Received Management frame processing
|
|
* Copyright (c) 2010-2015, Jouni Malinen <j@w1.fi>
|
|
*
|
|
* 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/defs.h"
|
|
#include "common/ieee802_11_defs.h"
|
|
#include "common/ieee802_11_common.h"
|
|
#include "common/wpa_common.h"
|
|
#include "crypto/aes.h"
|
|
#include "crypto/aes_siv.h"
|
|
#include "crypto/aes_wrap.h"
|
|
#include "wlantest.h"
|
|
|
|
|
|
static const char * mgmt_stype(u16 stype)
|
|
{
|
|
switch (stype) {
|
|
case WLAN_FC_STYPE_ASSOC_REQ:
|
|
return "ASSOC-REQ";
|
|
case WLAN_FC_STYPE_ASSOC_RESP:
|
|
return "ASSOC-RESP";
|
|
case WLAN_FC_STYPE_REASSOC_REQ:
|
|
return "REASSOC-REQ";
|
|
case WLAN_FC_STYPE_REASSOC_RESP:
|
|
return "REASSOC-RESP";
|
|
case WLAN_FC_STYPE_PROBE_REQ:
|
|
return "PROBE-REQ";
|
|
case WLAN_FC_STYPE_PROBE_RESP:
|
|
return "PROBE-RESP";
|
|
case WLAN_FC_STYPE_BEACON:
|
|
return "BEACON";
|
|
case WLAN_FC_STYPE_ATIM:
|
|
return "ATIM";
|
|
case WLAN_FC_STYPE_DISASSOC:
|
|
return "DISASSOC";
|
|
case WLAN_FC_STYPE_AUTH:
|
|
return "AUTH";
|
|
case WLAN_FC_STYPE_DEAUTH:
|
|
return "DEAUTH";
|
|
case WLAN_FC_STYPE_ACTION:
|
|
return "ACTION";
|
|
}
|
|
return "??";
|
|
}
|
|
|
|
|
|
static void rx_mgmt_beacon(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct ieee802_11_elems elems;
|
|
size_t offset;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
offset = mgmt->u.beacon.variable - data;
|
|
if (len < offset)
|
|
return;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (bss->proberesp_seen)
|
|
return; /* do not override with Beacon data */
|
|
bss->capab_info = le_to_host16(mgmt->u.beacon.capab_info);
|
|
if (ieee802_11_parse_elems(mgmt->u.beacon.variable, len - offset,
|
|
&elems, 0) == ParseFailed) {
|
|
if (bss->parse_error_reported)
|
|
return;
|
|
add_note(wt, MSG_INFO, "Invalid IEs in a Beacon frame from "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
bss->parse_error_reported = 1;
|
|
return;
|
|
}
|
|
|
|
bss_update(wt, bss, &elems);
|
|
}
|
|
|
|
|
|
static void rx_mgmt_probe_resp(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct ieee802_11_elems elems;
|
|
size_t offset;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
offset = mgmt->u.probe_resp.variable - data;
|
|
if (len < offset)
|
|
return;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
|
|
bss->counters[WLANTEST_BSS_COUNTER_PROBE_RESPONSE]++;
|
|
bss->capab_info = le_to_host16(mgmt->u.probe_resp.capab_info);
|
|
if (ieee802_11_parse_elems(mgmt->u.probe_resp.variable, len - offset,
|
|
&elems, 0) == ParseFailed) {
|
|
if (bss->parse_error_reported)
|
|
return;
|
|
add_note(wt, MSG_INFO, "Invalid IEs in a Probe Response frame "
|
|
"from " MACSTR, MAC2STR(mgmt->sa));
|
|
bss->parse_error_reported = 1;
|
|
return;
|
|
}
|
|
|
|
bss_update(wt, bss, &elems);
|
|
}
|
|
|
|
|
|
static void process_fils_auth(struct wlantest *wt, struct wlantest_bss *bss,
|
|
struct wlantest_sta *sta,
|
|
const struct ieee80211_mgmt *mgmt, size_t len)
|
|
{
|
|
struct ieee802_11_elems elems;
|
|
u16 trans;
|
|
struct wpa_ie_data data;
|
|
|
|
if (sta->auth_alg != WLAN_AUTH_FILS_SK ||
|
|
len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth))
|
|
return;
|
|
|
|
trans = le_to_host16(mgmt->u.auth.auth_transaction);
|
|
|
|
if (ieee802_11_parse_elems(mgmt->u.auth.variable,
|
|
len - IEEE80211_HDRLEN -
|
|
sizeof(mgmt->u.auth), &elems, 0) ==
|
|
ParseFailed)
|
|
return;
|
|
|
|
if (trans == 1) {
|
|
if (!elems.rsn_ie) {
|
|
add_note(wt, MSG_INFO,
|
|
"FILS Authentication frame missing RSNE");
|
|
return;
|
|
}
|
|
if (wpa_parse_wpa_ie_rsn(elems.rsn_ie - 2,
|
|
elems.rsn_ie_len + 2, &data) < 0) {
|
|
add_note(wt, MSG_INFO,
|
|
"Invalid RSNE in FILS Authentication frame");
|
|
return;
|
|
}
|
|
sta->key_mgmt = data.key_mgmt;
|
|
sta->pairwise_cipher = data.pairwise_cipher;
|
|
}
|
|
|
|
if (!elems.fils_nonce) {
|
|
add_note(wt, MSG_INFO,
|
|
"FILS Authentication frame missing nonce");
|
|
return;
|
|
}
|
|
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
os_memcpy(sta->anonce, elems.fils_nonce, FILS_NONCE_LEN);
|
|
else
|
|
os_memcpy(sta->snonce, elems.fils_nonce, FILS_NONCE_LEN);
|
|
}
|
|
|
|
|
|
static void rx_mgmt_auth(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
u16 alg, trans, status;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->da);
|
|
else
|
|
sta = sta_get(bss, mgmt->sa);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
if (len < 24 + 6) {
|
|
add_note(wt, MSG_INFO, "Too short Authentication frame from "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
alg = le_to_host16(mgmt->u.auth.auth_alg);
|
|
sta->auth_alg = alg;
|
|
trans = le_to_host16(mgmt->u.auth.auth_transaction);
|
|
status = le_to_host16(mgmt->u.auth.status_code);
|
|
|
|
wpa_printf(MSG_DEBUG, "AUTH " MACSTR " -> " MACSTR
|
|
" (alg=%u trans=%u status=%u)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), alg, trans, status);
|
|
|
|
if (alg == 0 && trans == 2 && status == 0) {
|
|
if (sta->state == STATE1) {
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 2 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE2;
|
|
}
|
|
}
|
|
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta->counters[WLANTEST_STA_COUNTER_AUTH_RX]++;
|
|
else
|
|
sta->counters[WLANTEST_STA_COUNTER_AUTH_TX]++;
|
|
|
|
process_fils_auth(wt, bss, sta, mgmt, len);
|
|
}
|
|
|
|
|
|
static void deauth_all_stas(struct wlantest *wt, struct wlantest_bss *bss)
|
|
{
|
|
struct wlantest_sta *sta;
|
|
dl_list_for_each(sta, &bss->sta, struct wlantest_sta, list) {
|
|
if (sta->state == STATE1)
|
|
continue;
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 1 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE1;
|
|
}
|
|
}
|
|
|
|
|
|
static void tdls_link_down(struct wlantest *wt, struct wlantest_bss *bss,
|
|
struct wlantest_sta *sta)
|
|
{
|
|
struct wlantest_tdls *tdls;
|
|
dl_list_for_each(tdls, &bss->tdls, struct wlantest_tdls, list) {
|
|
if ((tdls->init == sta || tdls->resp == sta) && tdls->link_up)
|
|
{
|
|
add_note(wt, MSG_DEBUG, "TDLS: Set link down based on "
|
|
"STA deauth/disassoc");
|
|
tdls->link_up = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_deauth(struct wlantest *wt, const u8 *data, size_t len,
|
|
int valid)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
u16 fc, reason;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->da);
|
|
else
|
|
sta = sta_get(bss, mgmt->sa);
|
|
|
|
if (len < 24 + 2) {
|
|
add_note(wt, MSG_INFO, "Too short Deauthentication frame from "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
reason = le_to_host16(mgmt->u.deauth.reason_code);
|
|
wpa_printf(MSG_DEBUG, "DEAUTH " MACSTR " -> " MACSTR
|
|
" (reason=%u) (valid=%d)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da),
|
|
reason, valid);
|
|
wpa_hexdump(MSG_MSGDUMP, "DEAUTH payload", data + 24, len - 24);
|
|
|
|
if (sta == NULL) {
|
|
if (valid && mgmt->da[0] == 0xff)
|
|
deauth_all_stas(wt, bss);
|
|
return;
|
|
}
|
|
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0) {
|
|
sta->counters[valid ? WLANTEST_STA_COUNTER_VALID_DEAUTH_RX :
|
|
WLANTEST_STA_COUNTER_INVALID_DEAUTH_RX]++;
|
|
if (sta->pwrmgt && !sta->pspoll)
|
|
sta->counters[WLANTEST_STA_COUNTER_DEAUTH_RX_ASLEEP]++;
|
|
else
|
|
sta->counters[WLANTEST_STA_COUNTER_DEAUTH_RX_AWAKE]++;
|
|
|
|
fc = le_to_host16(mgmt->frame_control);
|
|
if (!(fc & WLAN_FC_ISWEP) && reason == 6)
|
|
sta->counters[WLANTEST_STA_COUNTER_DEAUTH_RX_RC6]++;
|
|
else if (!(fc & WLAN_FC_ISWEP) && reason == 7)
|
|
sta->counters[WLANTEST_STA_COUNTER_DEAUTH_RX_RC7]++;
|
|
} else
|
|
sta->counters[valid ? WLANTEST_STA_COUNTER_VALID_DEAUTH_TX :
|
|
WLANTEST_STA_COUNTER_INVALID_DEAUTH_TX]++;
|
|
|
|
if (!valid) {
|
|
add_note(wt, MSG_INFO, "Do not change STA " MACSTR " State "
|
|
"since Disassociation frame was not protected "
|
|
"correctly", MAC2STR(sta->addr));
|
|
return;
|
|
}
|
|
|
|
if (sta->state != STATE1) {
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 1 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE1;
|
|
}
|
|
tdls_link_down(wt, bss, sta);
|
|
}
|
|
|
|
|
|
static const u8 * get_fils_session(const u8 *ies, size_t ies_len)
|
|
{
|
|
const u8 *ie, *end;
|
|
|
|
ie = ies;
|
|
end = ((const u8 *) ie) + ies_len;
|
|
while (ie + 1 < end) {
|
|
if (ie + 2 + ie[1] > end)
|
|
break;
|
|
if (ie[0] == WLAN_EID_EXTENSION &&
|
|
ie[1] >= 1 + FILS_SESSION_LEN &&
|
|
ie[2] == WLAN_EID_EXT_FILS_SESSION)
|
|
return ie;
|
|
ie += 2 + ie[1];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static int try_rmsk(struct wlantest *wt, struct wlantest_bss *bss,
|
|
struct wlantest_sta *sta, struct wlantest_pmk *pmk,
|
|
const u8 *frame_start, const u8 *frame_ad,
|
|
const u8 *frame_ad_end, const u8 *encr_end)
|
|
{
|
|
size_t pmk_len = 0;
|
|
u8 pmk_buf[PMK_LEN_MAX];
|
|
struct wpa_ptk ptk;
|
|
u8 ick[FILS_ICK_MAX_LEN];
|
|
size_t ick_len;
|
|
const u8 *aad[5];
|
|
size_t aad_len[5];
|
|
u8 buf[2000];
|
|
|
|
if (fils_rmsk_to_pmk(sta->key_mgmt, pmk->pmk, pmk->pmk_len,
|
|
sta->snonce, sta->anonce, NULL, 0,
|
|
pmk_buf, &pmk_len) < 0)
|
|
return -1;
|
|
|
|
if (fils_pmk_to_ptk(pmk_buf, pmk_len, sta->addr, bss->bssid,
|
|
sta->snonce, sta->anonce, NULL, 0,
|
|
&ptk, ick, &ick_len,
|
|
sta->key_mgmt, sta->pairwise_cipher,
|
|
NULL, NULL) < 0)
|
|
return -1;
|
|
|
|
/* Check AES-SIV decryption with the derived key */
|
|
|
|
/* AES-SIV AAD vectors */
|
|
|
|
/* The STA's MAC address */
|
|
aad[0] = sta->addr;
|
|
aad_len[0] = ETH_ALEN;
|
|
/* The AP's BSSID */
|
|
aad[1] = bss->bssid;
|
|
aad_len[1] = ETH_ALEN;
|
|
/* The STA's nonce */
|
|
aad[2] = sta->snonce;
|
|
aad_len[2] = FILS_NONCE_LEN;
|
|
/* The AP's nonce */
|
|
aad[3] = sta->anonce;
|
|
aad_len[3] = FILS_NONCE_LEN;
|
|
/*
|
|
* The (Re)Association Request frame from the Capability Information
|
|
* field to the FILS Session element (both inclusive).
|
|
*/
|
|
aad[4] = frame_ad;
|
|
aad_len[4] = frame_ad_end - frame_ad;
|
|
|
|
if (encr_end - frame_ad_end < AES_BLOCK_SIZE ||
|
|
encr_end - frame_ad_end > sizeof(buf))
|
|
return -1;
|
|
if (aes_siv_decrypt(ptk.kek, ptk.kek_len,
|
|
frame_ad_end, encr_end - frame_ad_end,
|
|
5, aad, aad_len, buf) < 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FILS: Derived PTK did not match AES-SIV data");
|
|
return -1;
|
|
}
|
|
|
|
add_note(wt, MSG_DEBUG, "Derived FILS PTK");
|
|
os_memcpy(&sta->ptk, &ptk, sizeof(ptk));
|
|
sta->ptk_set = 1;
|
|
sta->counters[WLANTEST_STA_COUNTER_PTK_LEARNED]++;
|
|
wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Request elements",
|
|
buf, encr_end - frame_ad_end - AES_BLOCK_SIZE);
|
|
|
|
if (wt->write_pcap_dumper || wt->pcapng) {
|
|
write_pcap_decrypted(wt, frame_start,
|
|
frame_ad_end - frame_start,
|
|
buf,
|
|
encr_end - frame_ad_end - AES_BLOCK_SIZE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void derive_fils_keys(struct wlantest *wt, struct wlantest_bss *bss,
|
|
struct wlantest_sta *sta, const u8 *frame_start,
|
|
const u8 *frame_ad, const u8 *frame_ad_end,
|
|
const u8 *encr_end)
|
|
{
|
|
struct wlantest_pmk *pmk;
|
|
|
|
wpa_printf(MSG_DEBUG, "Trying to derive PTK for " MACSTR
|
|
" from FILS rMSK", MAC2STR(sta->addr));
|
|
|
|
dl_list_for_each(pmk, &bss->pmk, struct wlantest_pmk,
|
|
list) {
|
|
wpa_printf(MSG_DEBUG, "Try per-BSS PMK");
|
|
if (try_rmsk(wt, bss, sta, pmk, frame_start, frame_ad,
|
|
frame_ad_end, encr_end) == 0)
|
|
return;
|
|
}
|
|
|
|
dl_list_for_each(pmk, &wt->pmk, struct wlantest_pmk, list) {
|
|
wpa_printf(MSG_DEBUG, "Try global PMK");
|
|
if (try_rmsk(wt, bss, sta, pmk, frame_start, frame_ad,
|
|
frame_ad_end, encr_end) == 0)
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_assoc_req(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
struct ieee802_11_elems elems;
|
|
const u8 *ie;
|
|
size_t ie_len;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
sta = sta_get(bss, mgmt->sa);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
if (len < 24 + 4) {
|
|
add_note(wt, MSG_INFO, "Too short Association Request frame "
|
|
"from " MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "ASSOCREQ " MACSTR " -> " MACSTR
|
|
" (capab=0x%x listen_int=%u)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da),
|
|
le_to_host16(mgmt->u.assoc_req.capab_info),
|
|
le_to_host16(mgmt->u.assoc_req.listen_interval));
|
|
|
|
sta->counters[WLANTEST_STA_COUNTER_ASSOCREQ_TX]++;
|
|
|
|
ie = mgmt->u.assoc_req.variable;
|
|
ie_len = len - (mgmt->u.assoc_req.variable - data);
|
|
|
|
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
|
|
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
|
|
|
|
session = get_fils_session(ie, ie_len);
|
|
if (session) {
|
|
frame_ad = (const u8 *) &mgmt->u.assoc_req.capab_info;
|
|
frame_ad_end = session + 2 + session[1];
|
|
encr_end = data + len;
|
|
derive_fils_keys(wt, bss, sta, data, frame_ad,
|
|
frame_ad_end, encr_end);
|
|
ie_len = session - ie;
|
|
}
|
|
}
|
|
|
|
if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) {
|
|
add_note(wt, MSG_INFO, "Invalid IEs in Association Request "
|
|
"frame from " MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
sta->assocreq_capab_info = le_to_host16(mgmt->u.assoc_req.capab_info);
|
|
sta->assocreq_listen_int =
|
|
le_to_host16(mgmt->u.assoc_req.listen_interval);
|
|
os_free(sta->assocreq_ies);
|
|
sta->assocreq_ies_len = len - (mgmt->u.assoc_req.variable - data);
|
|
sta->assocreq_ies = os_malloc(sta->assocreq_ies_len);
|
|
if (sta->assocreq_ies)
|
|
os_memcpy(sta->assocreq_ies, mgmt->u.assoc_req.variable,
|
|
sta->assocreq_ies_len);
|
|
|
|
sta_update_assoc(sta, &elems);
|
|
}
|
|
|
|
|
|
static void decrypt_fils_assoc_resp(struct wlantest *wt,
|
|
struct wlantest_bss *bss,
|
|
struct wlantest_sta *sta,
|
|
const u8 *frame_start, const u8 *frame_ad,
|
|
const u8 *frame_ad_end, const u8 *encr_end)
|
|
{
|
|
const u8 *aad[5];
|
|
size_t aad_len[5];
|
|
u8 buf[2000];
|
|
|
|
if (!sta->ptk_set)
|
|
return;
|
|
|
|
/* Check AES-SIV decryption with the derived key */
|
|
|
|
/* AES-SIV AAD vectors */
|
|
|
|
/* The AP's BSSID */
|
|
aad[0] = bss->bssid;
|
|
aad_len[0] = ETH_ALEN;
|
|
/* The STA's MAC address */
|
|
aad[1] = sta->addr;
|
|
aad_len[1] = ETH_ALEN;
|
|
/* The AP's nonce */
|
|
aad[2] = sta->anonce;
|
|
aad_len[2] = FILS_NONCE_LEN;
|
|
/* The STA's nonce */
|
|
aad[3] = sta->snonce;
|
|
aad_len[3] = FILS_NONCE_LEN;
|
|
/*
|
|
* The (Re)Association Response frame from the Capability Information
|
|
* field to the FILS Session element (both inclusive).
|
|
*/
|
|
aad[4] = frame_ad;
|
|
aad_len[4] = frame_ad_end - frame_ad;
|
|
|
|
if (encr_end - frame_ad_end < AES_BLOCK_SIZE ||
|
|
encr_end - frame_ad_end > sizeof(buf))
|
|
return;
|
|
if (aes_siv_decrypt(sta->ptk.kek, sta->ptk.kek_len,
|
|
frame_ad_end, encr_end - frame_ad_end,
|
|
5, aad, aad_len, buf) < 0) {
|
|
wpa_printf(MSG_DEBUG,
|
|
"FILS: Derived PTK did not match AES-SIV data");
|
|
return;
|
|
}
|
|
|
|
wpa_hexdump(MSG_DEBUG, "FILS: Decrypted Association Response elements",
|
|
buf, encr_end - frame_ad_end - AES_BLOCK_SIZE);
|
|
|
|
if (wt->write_pcap_dumper || wt->pcapng) {
|
|
write_pcap_decrypted(wt, frame_start,
|
|
frame_ad_end - frame_start,
|
|
buf,
|
|
encr_end - frame_ad_end - AES_BLOCK_SIZE);
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_assoc_resp(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
u16 capab, status, aid;
|
|
const u8 *ies;
|
|
size_t ies_len;
|
|
struct wpa_ft_ies parse;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
sta = sta_get(bss, mgmt->da);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
if (len < 24 + 6) {
|
|
add_note(wt, MSG_INFO, "Too short Association Response frame "
|
|
"from " MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
ies = mgmt->u.assoc_resp.variable;
|
|
ies_len = len - (mgmt->u.assoc_resp.variable - data);
|
|
|
|
capab = le_to_host16(mgmt->u.assoc_resp.capab_info);
|
|
status = le_to_host16(mgmt->u.assoc_resp.status_code);
|
|
aid = le_to_host16(mgmt->u.assoc_resp.aid);
|
|
|
|
wpa_printf(MSG_DEBUG, "ASSOCRESP " MACSTR " -> " MACSTR
|
|
" (capab=0x%x status=%u aid=%u)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status,
|
|
aid & 0x3fff);
|
|
|
|
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
|
|
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
|
|
|
|
session = get_fils_session(ies, ies_len);
|
|
if (session) {
|
|
frame_ad = (const u8 *) &mgmt->u.assoc_resp.capab_info;
|
|
frame_ad_end = session + 2 + session[1];
|
|
encr_end = data + len;
|
|
decrypt_fils_assoc_resp(wt, bss, sta, data, frame_ad,
|
|
frame_ad_end, encr_end);
|
|
ies_len = session - ies;
|
|
}
|
|
}
|
|
|
|
if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
|
|
struct ieee802_11_elems elems;
|
|
if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) ==
|
|
ParseFailed) {
|
|
add_note(wt, MSG_INFO, "Failed to parse IEs in "
|
|
"AssocResp from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
} else if (elems.timeout_int == NULL ||
|
|
elems.timeout_int[0] !=
|
|
WLAN_TIMEOUT_ASSOC_COMEBACK) {
|
|
add_note(wt, MSG_INFO, "No valid Timeout Interval IE "
|
|
"with Assoc Comeback time in AssocResp "
|
|
"(status=30) from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
} else {
|
|
sta->counters[
|
|
WLANTEST_STA_COUNTER_ASSOCRESP_COMEBACK]++;
|
|
}
|
|
}
|
|
|
|
if (status)
|
|
return;
|
|
|
|
if ((aid & 0xc000) != 0xc000) {
|
|
add_note(wt, MSG_DEBUG, "Two MSBs of the AID were not set to 1 "
|
|
"in Association Response from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
}
|
|
sta->aid = aid & 0xc000;
|
|
|
|
if (sta->state < STATE2) {
|
|
add_note(wt, MSG_DEBUG,
|
|
"STA " MACSTR " was not in State 2 when "
|
|
"getting associated", MAC2STR(sta->addr));
|
|
}
|
|
|
|
if (sta->state < STATE3) {
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 3 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE3;
|
|
}
|
|
|
|
if (wpa_ft_parse_ies(ies, ies_len, &parse, 0) == 0) {
|
|
if (parse.r0kh_id) {
|
|
os_memcpy(bss->r0kh_id, parse.r0kh_id,
|
|
parse.r0kh_id_len);
|
|
bss->r0kh_id_len = parse.r0kh_id_len;
|
|
}
|
|
if (parse.r1kh_id)
|
|
os_memcpy(bss->r1kh_id, parse.r1kh_id, FT_R1KH_ID_LEN);
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_reassoc_req(struct wlantest *wt, const u8 *data,
|
|
size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
struct ieee802_11_elems elems;
|
|
const u8 *ie;
|
|
size_t ie_len;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
sta = sta_get(bss, mgmt->sa);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
if (len < 24 + 4 + ETH_ALEN) {
|
|
add_note(wt, MSG_INFO, "Too short Reassociation Request frame "
|
|
"from " MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "REASSOCREQ " MACSTR " -> " MACSTR
|
|
" (capab=0x%x listen_int=%u current_ap=" MACSTR ")",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da),
|
|
le_to_host16(mgmt->u.reassoc_req.capab_info),
|
|
le_to_host16(mgmt->u.reassoc_req.listen_interval),
|
|
MAC2STR(mgmt->u.reassoc_req.current_ap));
|
|
|
|
sta->counters[WLANTEST_STA_COUNTER_REASSOCREQ_TX]++;
|
|
|
|
ie = mgmt->u.reassoc_req.variable;
|
|
ie_len = len - (mgmt->u.reassoc_req.variable - data);
|
|
|
|
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
|
|
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
|
|
|
|
session = get_fils_session(ie, ie_len);
|
|
if (session) {
|
|
frame_ad = (const u8 *) &mgmt->u.reassoc_req.capab_info;
|
|
frame_ad_end = session + 2 + session[1];
|
|
encr_end = data + len;
|
|
derive_fils_keys(wt, bss, sta, data, frame_ad,
|
|
frame_ad_end, encr_end);
|
|
ie_len = session - ie;
|
|
}
|
|
}
|
|
|
|
if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) {
|
|
add_note(wt, MSG_INFO, "Invalid IEs in Reassociation Request "
|
|
"frame from " MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
sta->assocreq_capab_info =
|
|
le_to_host16(mgmt->u.reassoc_req.capab_info);
|
|
sta->assocreq_listen_int =
|
|
le_to_host16(mgmt->u.reassoc_req.listen_interval);
|
|
os_free(sta->assocreq_ies);
|
|
sta->assocreq_ies_len = len - (mgmt->u.reassoc_req.variable - data);
|
|
sta->assocreq_ies = os_malloc(sta->assocreq_ies_len);
|
|
if (sta->assocreq_ies)
|
|
os_memcpy(sta->assocreq_ies, mgmt->u.reassoc_req.variable,
|
|
sta->assocreq_ies_len);
|
|
|
|
sta_update_assoc(sta, &elems);
|
|
}
|
|
|
|
|
|
static void rx_mgmt_reassoc_resp(struct wlantest *wt, const u8 *data,
|
|
size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
u16 capab, status, aid;
|
|
const u8 *ies;
|
|
size_t ies_len;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
sta = sta_get(bss, mgmt->da);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
if (len < 24 + 6) {
|
|
add_note(wt, MSG_INFO, "Too short Reassociation Response frame "
|
|
"from " MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
ies = mgmt->u.reassoc_resp.variable;
|
|
ies_len = len - (mgmt->u.reassoc_resp.variable - data);
|
|
|
|
capab = le_to_host16(mgmt->u.reassoc_resp.capab_info);
|
|
status = le_to_host16(mgmt->u.reassoc_resp.status_code);
|
|
aid = le_to_host16(mgmt->u.reassoc_resp.aid);
|
|
|
|
wpa_printf(MSG_DEBUG, "REASSOCRESP " MACSTR " -> " MACSTR
|
|
" (capab=0x%x status=%u aid=%u)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), capab, status,
|
|
aid & 0x3fff);
|
|
|
|
if (sta->auth_alg == WLAN_AUTH_FILS_SK) {
|
|
const u8 *session, *frame_ad, *frame_ad_end, *encr_end;
|
|
|
|
session = get_fils_session(ies, ies_len);
|
|
if (session) {
|
|
frame_ad = (const u8 *)
|
|
&mgmt->u.reassoc_resp.capab_info;
|
|
frame_ad_end = session + 2 + session[1];
|
|
encr_end = data + len;
|
|
decrypt_fils_assoc_resp(wt, bss, sta, data, frame_ad,
|
|
frame_ad_end, encr_end);
|
|
ies_len = session - ies;
|
|
}
|
|
}
|
|
|
|
if (status == WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
|
|
struct ieee802_11_elems elems;
|
|
|
|
if (ieee802_11_parse_elems(ies, ies_len, &elems, 0) ==
|
|
ParseFailed) {
|
|
add_note(wt, MSG_INFO, "Failed to parse IEs in "
|
|
"ReassocResp from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
} else if (elems.timeout_int == NULL ||
|
|
elems.timeout_int[0] !=
|
|
WLAN_TIMEOUT_ASSOC_COMEBACK) {
|
|
add_note(wt, MSG_INFO, "No valid Timeout Interval IE "
|
|
"with Assoc Comeback time in ReassocResp "
|
|
"(status=30) from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
} else {
|
|
sta->counters[
|
|
WLANTEST_STA_COUNTER_REASSOCRESP_COMEBACK]++;
|
|
}
|
|
}
|
|
|
|
if (status)
|
|
return;
|
|
|
|
if ((aid & 0xc000) != 0xc000) {
|
|
add_note(wt, MSG_DEBUG, "Two MSBs of the AID were not set to 1 "
|
|
"in Reassociation Response from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
}
|
|
sta->aid = aid & 0xc000;
|
|
|
|
if (sta->state < STATE2) {
|
|
add_note(wt, MSG_DEBUG,
|
|
"STA " MACSTR " was not in State 2 when "
|
|
"getting associated", MAC2STR(sta->addr));
|
|
}
|
|
|
|
if (sta->state < STATE3) {
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 3 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE3;
|
|
}
|
|
}
|
|
|
|
|
|
static void disassoc_all_stas(struct wlantest *wt, struct wlantest_bss *bss)
|
|
{
|
|
struct wlantest_sta *sta;
|
|
dl_list_for_each(sta, &bss->sta, struct wlantest_sta, list) {
|
|
if (sta->state <= STATE2)
|
|
continue;
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 2 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE2;
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_disassoc(struct wlantest *wt, const u8 *data, size_t len,
|
|
int valid)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
u16 fc, reason;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->da);
|
|
else
|
|
sta = sta_get(bss, mgmt->sa);
|
|
|
|
if (len < 24 + 2) {
|
|
add_note(wt, MSG_INFO, "Too short Disassociation frame from "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
reason = le_to_host16(mgmt->u.disassoc.reason_code);
|
|
wpa_printf(MSG_DEBUG, "DISASSOC " MACSTR " -> " MACSTR
|
|
" (reason=%u) (valid=%d)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da),
|
|
reason, valid);
|
|
wpa_hexdump(MSG_MSGDUMP, "DISASSOC payload", data + 24, len - 24);
|
|
|
|
if (sta == NULL) {
|
|
if (valid && mgmt->da[0] == 0xff)
|
|
disassoc_all_stas(wt, bss);
|
|
return;
|
|
}
|
|
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0) {
|
|
sta->counters[valid ? WLANTEST_STA_COUNTER_VALID_DISASSOC_RX :
|
|
WLANTEST_STA_COUNTER_INVALID_DISASSOC_RX]++;
|
|
if (sta->pwrmgt && !sta->pspoll)
|
|
sta->counters[
|
|
WLANTEST_STA_COUNTER_DISASSOC_RX_ASLEEP]++;
|
|
else
|
|
sta->counters[
|
|
WLANTEST_STA_COUNTER_DISASSOC_RX_AWAKE]++;
|
|
|
|
fc = le_to_host16(mgmt->frame_control);
|
|
if (!(fc & WLAN_FC_ISWEP) && reason == 6)
|
|
sta->counters[WLANTEST_STA_COUNTER_DISASSOC_RX_RC6]++;
|
|
else if (!(fc & WLAN_FC_ISWEP) && reason == 7)
|
|
sta->counters[WLANTEST_STA_COUNTER_DISASSOC_RX_RC7]++;
|
|
} else
|
|
sta->counters[valid ? WLANTEST_STA_COUNTER_VALID_DISASSOC_TX :
|
|
WLANTEST_STA_COUNTER_INVALID_DISASSOC_TX]++;
|
|
|
|
if (!valid) {
|
|
add_note(wt, MSG_INFO, "Do not change STA " MACSTR " State "
|
|
"since Disassociation frame was not protected "
|
|
"correctly", MAC2STR(sta->addr));
|
|
return;
|
|
}
|
|
|
|
if (sta->state < STATE2) {
|
|
add_note(wt, MSG_DEBUG,
|
|
"STA " MACSTR " was not in State 2 or 3 "
|
|
"when getting disassociated", MAC2STR(sta->addr));
|
|
}
|
|
|
|
if (sta->state > STATE2) {
|
|
add_note(wt, MSG_DEBUG, "STA " MACSTR
|
|
" moved to State 2 with " MACSTR,
|
|
MAC2STR(sta->addr), MAC2STR(bss->bssid));
|
|
sta->state = STATE2;
|
|
}
|
|
tdls_link_down(wt, bss, sta);
|
|
}
|
|
|
|
|
|
static void rx_mgmt_action_sa_query_req(struct wlantest *wt,
|
|
struct wlantest_sta *sta,
|
|
const struct ieee80211_mgmt *mgmt,
|
|
size_t len, int valid)
|
|
{
|
|
const u8 *rx_id;
|
|
u8 *id;
|
|
|
|
rx_id = (const u8 *) mgmt->u.action.u.sa_query_req.trans_id;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
id = sta->ap_sa_query_tr;
|
|
else
|
|
id = sta->sta_sa_query_tr;
|
|
add_note(wt, MSG_INFO, "SA Query Request " MACSTR " -> " MACSTR
|
|
" (trans_id=%02x%02x)%s",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), rx_id[0], rx_id[1],
|
|
valid ? "" : " (invalid protection)");
|
|
os_memcpy(id, mgmt->u.action.u.sa_query_req.trans_id, 2);
|
|
if (os_memcmp(mgmt->sa, sta->addr, ETH_ALEN) == 0)
|
|
sta->counters[valid ?
|
|
WLANTEST_STA_COUNTER_VALID_SAQUERYREQ_TX :
|
|
WLANTEST_STA_COUNTER_INVALID_SAQUERYREQ_TX]++;
|
|
else
|
|
sta->counters[valid ?
|
|
WLANTEST_STA_COUNTER_VALID_SAQUERYREQ_RX :
|
|
WLANTEST_STA_COUNTER_INVALID_SAQUERYREQ_RX]++;
|
|
}
|
|
|
|
|
|
static void rx_mgmt_action_sa_query_resp(struct wlantest *wt,
|
|
struct wlantest_sta *sta,
|
|
const struct ieee80211_mgmt *mgmt,
|
|
size_t len, int valid)
|
|
{
|
|
const u8 *rx_id;
|
|
u8 *id;
|
|
int match;
|
|
|
|
rx_id = (const u8 *) mgmt->u.action.u.sa_query_resp.trans_id;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
id = sta->sta_sa_query_tr;
|
|
else
|
|
id = sta->ap_sa_query_tr;
|
|
match = os_memcmp(rx_id, id, 2) == 0;
|
|
add_note(wt, MSG_INFO, "SA Query Response " MACSTR " -> " MACSTR
|
|
" (trans_id=%02x%02x; %s)%s",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da), rx_id[0], rx_id[1],
|
|
match ? "match" : "mismatch",
|
|
valid ? "" : " (invalid protection)");
|
|
if (os_memcmp(mgmt->sa, sta->addr, ETH_ALEN) == 0)
|
|
sta->counters[(valid && match) ?
|
|
WLANTEST_STA_COUNTER_VALID_SAQUERYRESP_TX :
|
|
WLANTEST_STA_COUNTER_INVALID_SAQUERYRESP_TX]++;
|
|
else
|
|
sta->counters[(valid && match) ?
|
|
WLANTEST_STA_COUNTER_VALID_SAQUERYRESP_RX :
|
|
WLANTEST_STA_COUNTER_INVALID_SAQUERYRESP_RX]++;
|
|
}
|
|
|
|
|
|
static void rx_mgmt_action_sa_query(struct wlantest *wt,
|
|
struct wlantest_sta *sta,
|
|
const struct ieee80211_mgmt *mgmt,
|
|
size_t len, int valid)
|
|
{
|
|
if (len < 24 + 2 + WLAN_SA_QUERY_TR_ID_LEN) {
|
|
add_note(wt, MSG_INFO, "Too short SA Query frame from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
if (len > 24 + 2 + WLAN_SA_QUERY_TR_ID_LEN) {
|
|
size_t elen = len - (24 + 2 + WLAN_SA_QUERY_TR_ID_LEN);
|
|
add_note(wt, MSG_INFO, "Unexpected %u octets of extra data at "
|
|
"the end of SA Query frame from " MACSTR,
|
|
(unsigned) elen, MAC2STR(mgmt->sa));
|
|
wpa_hexdump(MSG_INFO, "SA Query extra data",
|
|
((const u8 *) mgmt) + len - elen, elen);
|
|
}
|
|
|
|
switch (mgmt->u.action.u.sa_query_req.action) {
|
|
case WLAN_SA_QUERY_REQUEST:
|
|
rx_mgmt_action_sa_query_req(wt, sta, mgmt, len, valid);
|
|
break;
|
|
case WLAN_SA_QUERY_RESPONSE:
|
|
rx_mgmt_action_sa_query_resp(wt, sta, mgmt, len, valid);
|
|
break;
|
|
default:
|
|
add_note(wt, MSG_INFO, "Unexpected SA Query action value %u "
|
|
"from " MACSTR,
|
|
mgmt->u.action.u.sa_query_req.action,
|
|
MAC2STR(mgmt->sa));
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_action(struct wlantest *wt, const u8 *data, size_t len,
|
|
int valid)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
if (mgmt->da[0] & 0x01) {
|
|
add_note(wt, MSG_DEBUG, "Group addressed Action frame: DA="
|
|
MACSTR " SA=" MACSTR " BSSID=" MACSTR
|
|
" category=%u",
|
|
MAC2STR(mgmt->da), MAC2STR(mgmt->sa),
|
|
MAC2STR(mgmt->bssid), mgmt->u.action.category);
|
|
return; /* Ignore group addressed Action frames for now */
|
|
}
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->da);
|
|
else
|
|
sta = sta_get(bss, mgmt->sa);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
if (len < 24 + 1) {
|
|
add_note(wt, MSG_INFO, "Too short Action frame from " MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
return;
|
|
}
|
|
|
|
wpa_printf(MSG_DEBUG, "ACTION " MACSTR " -> " MACSTR
|
|
" (category=%u) (valid=%d)",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da),
|
|
mgmt->u.action.category, valid);
|
|
wpa_hexdump(MSG_MSGDUMP, "ACTION payload", data + 24, len - 24);
|
|
|
|
if (mgmt->u.action.category != WLAN_ACTION_PUBLIC &&
|
|
sta->state < STATE3) {
|
|
add_note(wt, MSG_INFO, "Action frame sent when STA is not in "
|
|
"State 3 (SA=" MACSTR " DATA=" MACSTR ")",
|
|
MAC2STR(mgmt->sa), MAC2STR(mgmt->da));
|
|
}
|
|
|
|
switch (mgmt->u.action.category) {
|
|
case WLAN_ACTION_SA_QUERY:
|
|
rx_mgmt_action_sa_query(wt, sta, mgmt, len, valid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static int check_mmie_mic(unsigned int mgmt_group_cipher,
|
|
const u8 *igtk, size_t igtk_len,
|
|
const u8 *data, size_t len)
|
|
{
|
|
u8 *buf;
|
|
u8 mic[16];
|
|
u16 fc;
|
|
const struct ieee80211_hdr *hdr;
|
|
int ret, mic_len;
|
|
|
|
if (!mgmt_group_cipher || igtk_len < 16)
|
|
return -1;
|
|
mic_len = mgmt_group_cipher == WPA_CIPHER_AES_128_CMAC ? 8 : 16;
|
|
|
|
if (len < 24 || len - 24 < mic_len)
|
|
return -1;
|
|
|
|
buf = os_malloc(len + 20 - 24);
|
|
if (buf == NULL)
|
|
return -1;
|
|
|
|
/* BIP AAD: FC(masked) A1 A2 A3 */
|
|
hdr = (const struct ieee80211_hdr *) data;
|
|
fc = le_to_host16(hdr->frame_control);
|
|
fc &= ~(WLAN_FC_RETRY | WLAN_FC_PWRMGT | WLAN_FC_MOREDATA);
|
|
WPA_PUT_LE16(buf, fc);
|
|
os_memcpy(buf + 2, hdr->addr1, 3 * ETH_ALEN);
|
|
|
|
/* Frame body with MMIE MIC masked to zero */
|
|
os_memcpy(buf + 20, data + 24, len - 24 - mic_len);
|
|
os_memset(buf + 20 + len - 24 - mic_len, 0, mic_len);
|
|
|
|
wpa_hexdump(MSG_MSGDUMP, "BIP: AAD|Body(masked)", buf, len + 20 - 24);
|
|
/* MIC = L(AES-128-CMAC(AAD || Frame Body(masked)), 0, 64) */
|
|
if (mgmt_group_cipher == WPA_CIPHER_AES_128_CMAC) {
|
|
ret = omac1_aes_128(igtk, buf, len + 20 - 24, mic);
|
|
} else if (mgmt_group_cipher == WPA_CIPHER_BIP_CMAC_256) {
|
|
ret = omac1_aes_256(igtk, buf, len + 20 - 24, mic);
|
|
} else if (mgmt_group_cipher == WPA_CIPHER_BIP_GMAC_128 ||
|
|
mgmt_group_cipher == WPA_CIPHER_BIP_GMAC_256) {
|
|
u8 nonce[12], *npos;
|
|
const u8 *ipn;
|
|
|
|
ipn = data + len - mic_len - 6;
|
|
|
|
/* Nonce: A2 | IPN */
|
|
os_memcpy(nonce, hdr->addr2, ETH_ALEN);
|
|
npos = nonce + ETH_ALEN;
|
|
*npos++ = ipn[5];
|
|
*npos++ = ipn[4];
|
|
*npos++ = ipn[3];
|
|
*npos++ = ipn[2];
|
|
*npos++ = ipn[1];
|
|
*npos++ = ipn[0];
|
|
|
|
ret = aes_gmac(igtk, igtk_len, nonce, sizeof(nonce),
|
|
buf, len + 20 - 24, mic);
|
|
} else {
|
|
ret = -1;
|
|
}
|
|
if (ret < 0) {
|
|
os_free(buf);
|
|
return -1;
|
|
}
|
|
|
|
os_free(buf);
|
|
|
|
if (os_memcmp(data + len - mic_len, mic, mic_len) != 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int check_bip(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
u16 fc, stype;
|
|
const u8 *mmie;
|
|
u16 keyid;
|
|
struct wlantest_bss *bss;
|
|
size_t mic_len;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
fc = le_to_host16(mgmt->frame_control);
|
|
stype = WLAN_FC_GET_STYPE(fc);
|
|
|
|
if (stype == WLAN_FC_STYPE_ACTION) {
|
|
if (len < 24 + 1)
|
|
return 0;
|
|
if (mgmt->u.action.category == WLAN_ACTION_PUBLIC)
|
|
return 0; /* Not a robust management frame */
|
|
}
|
|
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return 0; /* No key known yet */
|
|
|
|
mic_len = bss->mgmt_group_cipher == WPA_CIPHER_AES_128_CMAC ? 8 : 16;
|
|
|
|
if (len < 24 + 10 + mic_len ||
|
|
data[len - (10 + mic_len)] != WLAN_EID_MMIE ||
|
|
data[len - (10 + mic_len - 1)] != 8 + mic_len) {
|
|
/* No MMIE */
|
|
if (bss->rsn_capab & WPA_CAPABILITY_MFPC) {
|
|
add_note(wt, MSG_INFO, "Robust group-addressed "
|
|
"management frame sent without BIP by "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
bss->counters[WLANTEST_BSS_COUNTER_MISSING_BIP_MMIE]++;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
mmie = data + len - (8 + mic_len);
|
|
keyid = WPA_GET_LE16(mmie);
|
|
if (keyid & 0xf000) {
|
|
add_note(wt, MSG_INFO, "MMIE KeyID reserved bits not zero "
|
|
"(%04x) from " MACSTR, keyid, MAC2STR(mgmt->sa));
|
|
keyid &= 0x0fff;
|
|
}
|
|
if (keyid < 4 || keyid > 5) {
|
|
add_note(wt, MSG_INFO, "Unexpected MMIE KeyID %u from " MACSTR,
|
|
keyid, MAC2STR(mgmt->sa));
|
|
bss->counters[WLANTEST_BSS_COUNTER_INVALID_BIP_MMIE]++;
|
|
return 0;
|
|
}
|
|
wpa_printf(MSG_DEBUG, "MMIE KeyID %u", keyid);
|
|
wpa_hexdump(MSG_MSGDUMP, "MMIE IPN", mmie + 2, 6);
|
|
wpa_hexdump(MSG_MSGDUMP, "MMIE MIC", mmie + 8, mic_len);
|
|
|
|
if (!bss->igtk_len[keyid]) {
|
|
add_note(wt, MSG_DEBUG, "No IGTK known to validate BIP frame");
|
|
return 0;
|
|
}
|
|
|
|
if (os_memcmp(mmie + 2, bss->ipn[keyid], 6) <= 0) {
|
|
add_note(wt, MSG_INFO, "BIP replay detected: SA=" MACSTR,
|
|
MAC2STR(mgmt->sa));
|
|
wpa_hexdump(MSG_INFO, "RX IPN", mmie + 2, 6);
|
|
wpa_hexdump(MSG_INFO, "Last RX IPN", bss->ipn[keyid], 6);
|
|
}
|
|
|
|
if (check_mmie_mic(bss->mgmt_group_cipher, bss->igtk[keyid],
|
|
bss->igtk_len[keyid], data, len) < 0) {
|
|
add_note(wt, MSG_INFO, "Invalid MMIE MIC in a frame from "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
bss->counters[WLANTEST_BSS_COUNTER_INVALID_BIP_MMIE]++;
|
|
return -1;
|
|
}
|
|
|
|
add_note(wt, MSG_DEBUG, "Valid MMIE MIC");
|
|
os_memcpy(bss->ipn[keyid], mmie + 2, 6);
|
|
bss->counters[WLANTEST_BSS_COUNTER_VALID_BIP_MMIE]++;
|
|
|
|
if (stype == WLAN_FC_STYPE_DEAUTH)
|
|
bss->counters[WLANTEST_BSS_COUNTER_BIP_DEAUTH]++;
|
|
else if (stype == WLAN_FC_STYPE_DISASSOC)
|
|
bss->counters[WLANTEST_BSS_COUNTER_BIP_DISASSOC]++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static u8 * mgmt_ccmp_decrypt(struct wlantest *wt, const u8 *data, size_t len,
|
|
size_t *dlen)
|
|
{
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
const struct ieee80211_hdr *hdr;
|
|
int keyid;
|
|
u8 *decrypted, *frame = NULL;
|
|
u8 pn[6], *rsc;
|
|
|
|
hdr = (const struct ieee80211_hdr *) data;
|
|
bss = bss_get(wt, hdr->addr3);
|
|
if (bss == NULL)
|
|
return NULL;
|
|
if (os_memcmp(hdr->addr1, hdr->addr3, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, hdr->addr2);
|
|
else
|
|
sta = sta_get(bss, hdr->addr1);
|
|
if (sta == NULL || !sta->ptk_set) {
|
|
add_note(wt, MSG_MSGDUMP, "No PTK known to decrypt the frame");
|
|
return NULL;
|
|
}
|
|
|
|
if (len < 24 + 4)
|
|
return NULL;
|
|
|
|
if (!(data[24 + 3] & 0x20)) {
|
|
add_note(wt, MSG_INFO, "Expected CCMP frame from " MACSTR
|
|
" did not have ExtIV bit set to 1",
|
|
MAC2STR(hdr->addr2));
|
|
return NULL;
|
|
}
|
|
|
|
if (data[24 + 2] != 0 || (data[24 + 3] & 0x1f) != 0) {
|
|
add_note(wt, MSG_INFO, "CCMP mgmt frame from " MACSTR " used "
|
|
"non-zero reserved bit", MAC2STR(hdr->addr2));
|
|
}
|
|
|
|
keyid = data[24 + 3] >> 6;
|
|
if (keyid != 0) {
|
|
add_note(wt, MSG_INFO, "Unexpected non-zero KeyID %d in "
|
|
"individually addressed Management frame from "
|
|
MACSTR, keyid, MAC2STR(hdr->addr2));
|
|
}
|
|
|
|
if (os_memcmp(hdr->addr1, hdr->addr3, ETH_ALEN) == 0)
|
|
rsc = sta->rsc_tods[16];
|
|
else
|
|
rsc = sta->rsc_fromds[16];
|
|
|
|
ccmp_get_pn(pn, data + 24);
|
|
if (os_memcmp(pn, rsc, 6) <= 0) {
|
|
u16 seq_ctrl = le_to_host16(hdr->seq_ctrl);
|
|
add_note(wt, MSG_INFO, "CCMP/TKIP replay detected: A1=" MACSTR
|
|
" A2=" MACSTR " A3=" MACSTR " seq=%u frag=%u%s",
|
|
MAC2STR(hdr->addr1), MAC2STR(hdr->addr2),
|
|
MAC2STR(hdr->addr3),
|
|
WLAN_GET_SEQ_SEQ(seq_ctrl),
|
|
WLAN_GET_SEQ_FRAG(seq_ctrl),
|
|
(le_to_host16(hdr->frame_control) & WLAN_FC_RETRY) ?
|
|
" Retry" : "");
|
|
wpa_hexdump(MSG_INFO, "RX PN", pn, 6);
|
|
wpa_hexdump(MSG_INFO, "RSC", rsc, 6);
|
|
}
|
|
|
|
decrypted = ccmp_decrypt(sta->ptk.tk, hdr, data + 24, len - 24, dlen);
|
|
if (decrypted) {
|
|
os_memcpy(rsc, pn, 6);
|
|
frame = os_malloc(24 + *dlen);
|
|
if (frame) {
|
|
os_memcpy(frame, data, 24);
|
|
os_memcpy(frame + 24, decrypted, *dlen);
|
|
*dlen += 24;
|
|
}
|
|
} else {
|
|
/* Assume the frame was corrupted and there was no FCS to check.
|
|
* Allow retry of this particular frame to be processed so that
|
|
* it could end up getting decrypted if it was received without
|
|
* corruption. */
|
|
sta->allow_duplicate = 1;
|
|
}
|
|
|
|
os_free(decrypted);
|
|
|
|
return frame;
|
|
}
|
|
|
|
|
|
static int check_mgmt_ccmp(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
u16 fc;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) data;
|
|
fc = le_to_host16(mgmt->frame_control);
|
|
|
|
if (WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_ACTION) {
|
|
if (len > 24 &&
|
|
mgmt->u.action.category == WLAN_ACTION_PUBLIC)
|
|
return 0; /* Not a robust management frame */
|
|
}
|
|
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return 0;
|
|
if (os_memcmp(mgmt->da, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->sa);
|
|
else
|
|
sta = sta_get(bss, mgmt->da);
|
|
if (sta == NULL)
|
|
return 0;
|
|
|
|
if ((sta->rsn_capab & WPA_CAPABILITY_MFPC) &&
|
|
(sta->state == STATE3 ||
|
|
WLAN_FC_GET_STYPE(fc) == WLAN_FC_STYPE_ACTION)) {
|
|
add_note(wt, MSG_INFO, "Robust individually-addressed "
|
|
"management frame sent without CCMP by "
|
|
MACSTR, MAC2STR(mgmt->sa));
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void rx_mgmt(struct wlantest *wt, const u8 *data, size_t len)
|
|
{
|
|
const struct ieee80211_hdr *hdr;
|
|
u16 fc, stype;
|
|
int valid = 1;
|
|
u8 *decrypted = NULL;
|
|
size_t dlen;
|
|
|
|
if (len < 24)
|
|
return;
|
|
|
|
hdr = (const struct ieee80211_hdr *) data;
|
|
fc = le_to_host16(hdr->frame_control);
|
|
wt->rx_mgmt++;
|
|
stype = WLAN_FC_GET_STYPE(fc);
|
|
|
|
if ((hdr->addr1[0] & 0x01) &&
|
|
(stype == WLAN_FC_STYPE_DEAUTH ||
|
|
stype == WLAN_FC_STYPE_DISASSOC ||
|
|
stype == WLAN_FC_STYPE_ACTION)) {
|
|
if (check_bip(wt, data, len) < 0)
|
|
valid = 0;
|
|
}
|
|
|
|
wpa_printf((stype == WLAN_FC_STYPE_BEACON ||
|
|
stype == WLAN_FC_STYPE_PROBE_RESP ||
|
|
stype == WLAN_FC_STYPE_PROBE_REQ) ?
|
|
MSG_EXCESSIVE : MSG_MSGDUMP,
|
|
"MGMT %s%s%s DA=" MACSTR " SA=" MACSTR " BSSID=" MACSTR,
|
|
mgmt_stype(stype),
|
|
fc & WLAN_FC_PWRMGT ? " PwrMgt" : "",
|
|
fc & WLAN_FC_ISWEP ? " Prot" : "",
|
|
MAC2STR(hdr->addr1), MAC2STR(hdr->addr2),
|
|
MAC2STR(hdr->addr3));
|
|
|
|
if ((fc & WLAN_FC_ISWEP) &&
|
|
!(hdr->addr1[0] & 0x01) &&
|
|
(stype == WLAN_FC_STYPE_DEAUTH ||
|
|
stype == WLAN_FC_STYPE_DISASSOC ||
|
|
stype == WLAN_FC_STYPE_ACTION)) {
|
|
decrypted = mgmt_ccmp_decrypt(wt, data, len, &dlen);
|
|
if (decrypted) {
|
|
write_pcap_decrypted(wt, decrypted, dlen, NULL, 0);
|
|
data = decrypted;
|
|
len = dlen;
|
|
} else
|
|
valid = 0;
|
|
}
|
|
|
|
if (!(fc & WLAN_FC_ISWEP) &&
|
|
!(hdr->addr1[0] & 0x01) &&
|
|
(stype == WLAN_FC_STYPE_DEAUTH ||
|
|
stype == WLAN_FC_STYPE_DISASSOC ||
|
|
stype == WLAN_FC_STYPE_ACTION)) {
|
|
if (check_mgmt_ccmp(wt, data, len) < 0)
|
|
valid = 0;
|
|
}
|
|
|
|
switch (stype) {
|
|
case WLAN_FC_STYPE_BEACON:
|
|
rx_mgmt_beacon(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_PROBE_RESP:
|
|
rx_mgmt_probe_resp(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_AUTH:
|
|
rx_mgmt_auth(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_DEAUTH:
|
|
rx_mgmt_deauth(wt, data, len, valid);
|
|
break;
|
|
case WLAN_FC_STYPE_ASSOC_REQ:
|
|
rx_mgmt_assoc_req(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_ASSOC_RESP:
|
|
rx_mgmt_assoc_resp(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_REASSOC_REQ:
|
|
rx_mgmt_reassoc_req(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_REASSOC_RESP:
|
|
rx_mgmt_reassoc_resp(wt, data, len);
|
|
break;
|
|
case WLAN_FC_STYPE_DISASSOC:
|
|
rx_mgmt_disassoc(wt, data, len, valid);
|
|
break;
|
|
case WLAN_FC_STYPE_ACTION:
|
|
rx_mgmt_action(wt, data, len, valid);
|
|
break;
|
|
}
|
|
|
|
os_free(decrypted);
|
|
|
|
wt->last_mgmt_valid = valid;
|
|
}
|
|
|
|
|
|
static void rx_mgmt_deauth_ack(struct wlantest *wt,
|
|
const struct ieee80211_hdr *hdr)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) hdr;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->da);
|
|
else
|
|
sta = sta_get(bss, mgmt->sa);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
add_note(wt, MSG_DEBUG, "DEAUTH from " MACSTR " acknowledged by "
|
|
MACSTR, MAC2STR(mgmt->sa), MAC2STR(mgmt->da));
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0) {
|
|
int c;
|
|
c = wt->last_mgmt_valid ?
|
|
WLANTEST_STA_COUNTER_VALID_DEAUTH_RX_ACK :
|
|
WLANTEST_STA_COUNTER_INVALID_DEAUTH_RX_ACK;
|
|
sta->counters[c]++;
|
|
}
|
|
}
|
|
|
|
|
|
static void rx_mgmt_disassoc_ack(struct wlantest *wt,
|
|
const struct ieee80211_hdr *hdr)
|
|
{
|
|
const struct ieee80211_mgmt *mgmt;
|
|
struct wlantest_bss *bss;
|
|
struct wlantest_sta *sta;
|
|
|
|
mgmt = (const struct ieee80211_mgmt *) hdr;
|
|
bss = bss_get(wt, mgmt->bssid);
|
|
if (bss == NULL)
|
|
return;
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0)
|
|
sta = sta_get(bss, mgmt->da);
|
|
else
|
|
sta = sta_get(bss, mgmt->sa);
|
|
if (sta == NULL)
|
|
return;
|
|
|
|
add_note(wt, MSG_DEBUG, "DISASSOC from " MACSTR " acknowledged by "
|
|
MACSTR, MAC2STR(mgmt->sa), MAC2STR(mgmt->da));
|
|
if (os_memcmp(mgmt->sa, mgmt->bssid, ETH_ALEN) == 0) {
|
|
int c;
|
|
c = wt->last_mgmt_valid ?
|
|
WLANTEST_STA_COUNTER_VALID_DISASSOC_RX_ACK :
|
|
WLANTEST_STA_COUNTER_INVALID_DISASSOC_RX_ACK;
|
|
sta->counters[c]++;
|
|
}
|
|
}
|
|
|
|
|
|
void rx_mgmt_ack(struct wlantest *wt, const struct ieee80211_hdr *hdr)
|
|
{
|
|
u16 fc, stype;
|
|
fc = le_to_host16(hdr->frame_control);
|
|
stype = WLAN_FC_GET_STYPE(fc);
|
|
|
|
wpa_printf(MSG_MSGDUMP, "MGMT ACK: stype=%u a1=" MACSTR " a2=" MACSTR
|
|
" a3=" MACSTR,
|
|
stype, MAC2STR(hdr->addr1), MAC2STR(hdr->addr2),
|
|
MAC2STR(hdr->addr3));
|
|
|
|
switch (stype) {
|
|
case WLAN_FC_STYPE_DEAUTH:
|
|
rx_mgmt_deauth_ack(wt, hdr);
|
|
break;
|
|
case WLAN_FC_STYPE_DISASSOC:
|
|
rx_mgmt_disassoc_ack(wt, hdr);
|
|
break;
|
|
}
|
|
}
|