fix(wps): harden enrollee WSC fragment handling

This commit is contained in:
Kapil Gupta
2026-03-27 13:59:55 +05:30
parent 72a0c99064
commit d388fc268d
2 changed files with 105 additions and 104 deletions
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2019-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2019-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -376,53 +376,54 @@ static int wps_send_frag_ack(u8 id)
return ret;
}
static int wps_enrollee_process_msg_frag(struct wpabuf **buf, int tot_len, u8 *frag_data, int frag_len, u8 flag)
static int wps_eap_wsc_process_cont(struct wps_eap_wsc_frag_data *frag,
const u8 *buf, size_t len, u8 op_code)
{
struct wps_sm *sm = wps_sm_get();
u8 identifier;
if (!sm || !buf || !frag_data) {
wpa_printf(MSG_ERROR, "WPS: %s: Invalid arguments", __func__);
return ESP_FAIL;
if (op_code != frag->in_op_code) {
wpa_printf(MSG_DEBUG, "EAP-WSC: Unexpected Op-Code %d in fragment "
"(expected %d)", op_code, frag->in_op_code);
return -1;
}
identifier = sm->current_identifier;
if (len > wpabuf_tailroom(frag->in_buf)) {
wpa_printf(MSG_DEBUG, "EAP-WSC: Fragment overflow");
return -1;
}
if (*buf == NULL) {
if (frag_len < 0 || tot_len < frag_len) {
wpa_printf(MSG_ERROR, "WPS: Invalid first fragment length");
return ESP_FAIL;
wpabuf_put_data(frag->in_buf, buf, len);
wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes, waiting for %lu "
"bytes more", (unsigned long) len,
(unsigned long) wpabuf_tailroom(frag->in_buf));
return 0;
}
static int wps_eap_wsc_process_fragment(struct wps_eap_wsc_frag_data *frag,
u8 flags, u8 op_code,
u16 message_length,
const u8 *buf, size_t len)
{
if (frag->in_buf == NULL && !(flags & WSC_FLAGS_LF)) {
wpa_printf(MSG_DEBUG, "EAP-WSC: No Message Length field in a "
"fragmented packet");
return -1;
}
if (frag->in_buf == NULL) {
frag->in_buf = wpabuf_alloc(message_length);
if (frag->in_buf == NULL) {
wpa_printf(MSG_DEBUG, "EAP-WSC: No memory for message");
return -1;
}
*buf = wpabuf_alloc(tot_len);
if (*buf == NULL) {
return ESP_ERR_NO_MEM;
}
wpabuf_put_data(*buf, frag_data, frag_len);
return wps_send_frag_ack(identifier);
frag->in_op_code = op_code;
wpabuf_put_data(frag->in_buf, buf, len);
wpa_printf(MSG_DEBUG, "EAP-WSC: Received %lu bytes in first "
"fragment, waiting for %lu bytes more",
(unsigned long) len,
(unsigned long) wpabuf_tailroom(frag->in_buf));
}
if (flag & WPS_MSG_FLAG_LEN) {
wpa_printf(MSG_ERROR, "WPS: %s: Invalid fragment flag: 0x%02x", __func__, flag);
wpabuf_free(*buf);
*buf = NULL;
return ESP_FAIL;
}
if (frag_len < 0 || wpabuf_len(*buf) + frag_len > tot_len) {
wpa_printf(MSG_ERROR, "WPS: Invalid subsequent fragment length");
wpabuf_free(*buf);
*buf = NULL;
return ESP_FAIL;
}
wpabuf_put_data(*buf, frag_data, frag_len);
if (flag & WPS_MSG_FLAG_MORE) {
return wps_send_frag_ack(identifier);
}
return ESP_OK;
return 0;
}
static void wifi_station_wps_post_m8_timeout(void *data, void *user_ctx)
@@ -436,29 +437,31 @@ static void wifi_station_wps_post_m8_timeout(void *data, void *user_ctx)
sm->post_m8_recv = false;
wps_finish();
}
};
}
static int wps_process_wps_mX_req(u8 *ubuf, int len, enum wps_process_res *res)
{
struct wps_sm *sm = wps_sm_get();
static struct wpabuf *wps_buf = NULL;
struct wps_eap_wsc_frag_data *frag;
struct eap_expand *expd;
int tlen = 0;
u8 *tbuf;
u8 flag;
int frag_len;
u16 be_tot_len = 0;
const u8 *pos, *end;
u8 flags;
u16 message_length = 0;
struct wpabuf tmpbuf;
if (!sm || !res) {
return ESP_FAIL;
}
frag = &sm->wsc_frag;
if (len < (int)(sizeof(struct eap_expand) + 1)) {
wpa_printf(MSG_ERROR, "WPS: Truncated EAP-Expanded header");
return ESP_FAIL;
}
expd = (struct eap_expand *) ubuf;
wpa_printf(MSG_DEBUG, "WPS: Processing WSC message (len=%d, total_len=%d)", len, tlen);
pos = ubuf + sizeof(struct eap_expand);
end = ubuf + len;
if (sm->state == WAIT_START) {
if (expd->opcode != WSC_Start) {
@@ -473,64 +476,56 @@ static int wps_process_wps_mX_req(u8 *ubuf, int len, enum wps_process_res *res)
return ESP_ERR_INVALID_STATE;
}
flag = *(u8 *)(ubuf + sizeof(struct eap_expand));
if (flag & WPS_MSG_FLAG_LEN) {
if (len < (int)(sizeof(struct eap_expand) + 1 + 2)) {
wpa_printf(MSG_ERROR, "WPS: Missing total length field");
flags = *pos++;
if (flags & WSC_FLAGS_LF) {
if (end - pos < 2) {
wpa_printf(MSG_ERROR, "WPS: Message underflow");
return ESP_FAIL;
}
tbuf = ubuf + sizeof(struct eap_expand) + 1 + 2; // includes 2-byte total length
frag_len = len - (sizeof(struct eap_expand) + 1 + 2);
be_tot_len = *(u16 *)(ubuf + sizeof(struct eap_expand) + 1);
tlen = ((be_tot_len & 0xff) << 8) | ((be_tot_len >> 8) & 0xff);
} else {
tbuf = ubuf + sizeof(struct eap_expand) + 1;
frag_len = len - (sizeof(struct eap_expand) + 1);
tlen = frag_len;
}
message_length = WPA_GET_BE16(pos);
pos += 2;
if (frag_len < 0 || tlen < 0 || ((flag & WPS_MSG_FLAG_LEN) && tlen < frag_len)) {
wpa_printf(MSG_ERROR, "WPS: Invalid fragment sizes");
if (wps_buf) {
wpabuf_free(wps_buf);
wps_buf = NULL;
}
return ESP_FAIL;
}
if (tlen > 50000) {
wpa_printf(MSG_ERROR, "WPS: Invalid EAP-WSC message length");
return ESP_FAIL;
}
if ((flag & WPS_MSG_FLAG_MORE) || wps_buf != NULL) {//frag msg
wpa_printf(MSG_DEBUG, "WPS: Received fragment (id=%d, flag=0x%x, frag_len=%d, total_len=%d)", sm->current_identifier, flag, frag_len, tlen);
if (ESP_OK != wps_enrollee_process_msg_frag(&wps_buf, tlen, tbuf, frag_len, flag)) {
if (wps_buf) {
wpabuf_free(wps_buf);
wps_buf = NULL;
}
if (message_length < (u16)(end - pos) || message_length > 50000) {
wpa_printf(MSG_ERROR, "WPS: Invalid Message Length");
return ESP_FAIL;
}
if (flag & WPS_MSG_FLAG_MORE) {
*res = WPS_FRAGMENT;
return ESP_OK;
}
} else { //not frag msg
if (wps_buf) {//if something wrong, frag msg buf is not freed, free first
wpa_printf(MSG_ERROR, "WPS: Fragment buffer not freed before receiving new message");
wpabuf_free(wps_buf);
wps_buf = NULL;
}
wps_buf = wpabuf_alloc_copy(tbuf, tlen);
}
if (!wps_buf) {
wpa_printf(MSG_DEBUG, "WPS: Received packet: Op-Code %d Flags 0x%x "
"Message Length %d", expd->opcode, flags, message_length);
if (frag->in_buf &&
wps_eap_wsc_process_cont(frag, pos, end - pos, expd->opcode) < 0) {
wpabuf_free(frag->in_buf);
frag->in_buf = NULL;
return ESP_FAIL;
}
if (flags & WSC_FLAGS_MF) {
if (wps_eap_wsc_process_fragment(frag, flags, expd->opcode,
message_length, pos,
end - pos) < 0) {
wpabuf_free(frag->in_buf);
frag->in_buf = NULL;
return ESP_FAIL;
}
if (wps_send_frag_ack(sm->current_identifier) != ESP_OK) {
wpabuf_free(frag->in_buf);
frag->in_buf = NULL;
return ESP_FAIL;
}
*res = WPS_FRAGMENT;
return ESP_OK;
}
if (frag->in_buf == NULL) {
wpabuf_set(&tmpbuf, pos, end - pos);
frag->in_buf = &tmpbuf;
}
eloop_cancel_timeout(wifi_station_wps_msg_timeout, NULL, NULL);
*res = wps_enrollee_process_msg(sm->wps, expd->opcode, wps_buf);
*res = wps_enrollee_process_msg(sm->wps, expd->opcode, frag->in_buf);
if (*res == WPS_CONTINUE && sm->wps->state == WPS_MSG_DONE) {
wpa_printf(MSG_DEBUG, "WPS: M8 processed, starting workaround timer");
@@ -543,10 +538,10 @@ static int wps_process_wps_mX_req(u8 *ubuf, int len, enum wps_process_res *res)
sm->state = WPA_FAIL;
}
if (wps_buf) {
wpabuf_free(wps_buf);
wps_buf = NULL;
if (frag->in_buf != &tmpbuf) {
wpabuf_free(frag->in_buf);
}
frag->in_buf = NULL;
return ESP_OK;
}
@@ -1343,6 +1338,10 @@ static int wifi_station_wps_deinit(void)
wps_deinit(sm->wps);
sm->wps = NULL;
}
if (sm->wsc_frag.in_buf) {
wpabuf_free(sm->wsc_frag.in_buf);
sm->wsc_frag.in_buf = NULL;
}
if (s_wps_sm_cb) {
s_wps_sm_cb->wps_sm_notify_deauth = NULL;
os_free(s_wps_sm_cb);
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -9,11 +9,7 @@
#include "wps/wps.h"
#include "wps/wps_attr_parse.h"
/* WPS message flag */
enum wps_msg_flag {
WPS_MSG_FLAG_MORE = 0x01,
WPS_MSG_FLAG_LEN = 0x02
};
struct wpabuf;
enum wps_reg_sig_type {
SIG_WPS_REG_ENABLE = 1, //1
@@ -41,6 +37,11 @@ struct discard_ap_list_t {
u8 bssid[6];
};
struct wps_eap_wsc_frag_data {
struct wpabuf *in_buf;
enum wsc_op_code in_op_code;
};
struct wps_sm {
u8 state;
struct wps_config *wps_cfg;
@@ -61,6 +62,7 @@ struct wps_sm {
u8 discover_ssid_cnt;
bool wps_pbc_overlap;
bool post_m8_recv;
struct wps_eap_wsc_frag_data wsc_frag;
};
#define API_MUTEX_TAKE() do {\