[PATCH] wifi: nxpwifi: validate variable IE lengths in beacon parser
From: Aamir Ahmed
Date: Sun Sep 06 2026 - 07:58:46 EST
nxpwifi_update_bss_desc_with_ie() stores pointers to the HT, VHT and HE
capability and operation elements, to the operating mode notification
and to the 20/40 BSS coexistence element, taken from a beacon or probe
response, without checking that each element is long enough for the
fixed size structure or field the driver later dereferences it as.
bss_desc->beacon_buf is a tight kmemdup() of the on-air elements in
nxpwifi_fill_new_bss_desc(), so a truncated element leaves the stored
pointer short of the structure and the consumers read past the end of
that allocation when the descriptor is used at association time:
nxpwifi_cmd_append_11n_tlv() memcpy()s sizeof(struct ieee80211_ht_cap)
from bcn_ht_cap, reads bcn_ht_oper->ht_param and ->primary_chan, and
memcpy()s one byte from bcn_bss_co_2040 + sizeof(struct element);
nxpwifi_cmd_append_11ac_tlv() memcpy()s from bcn_vht_cap and
bcn_vht_oper and reads oper_mode->oper_mode;
nxpwifi_is_ap_11ax_twt_supported() reads bcn_he_cap->mac_cap_info[0]
and bcn_ext_cap->data[9].
An element carrying a zero length placed last in the frame leaves the
stored pointer at the end of the allocation, so the read is entirely out
of bounds. The bytes read for the HT and VHT capabilities and for the
20/40 coexistence element are also copied into the association request,
so they are disclosed to the AP that supplied the frame. A rogue access
point in radio range can trigger this when the victim associates.
Reject the frame with -EINVAL when any of these elements is shorter than
the structure the driver reads, matching the length validation the FH,
DS and CF parameter set cases in the same parser already perform. The
operating mode notification pointer includes the element header, so it
is checked against total_ie_len. The HT and VHT pointers skip the header
and the HE pointers additionally skip the extension ID, so those are
checked against element_len.
Commit 8e4f5ca8bf67 ("wifi: nxpwifi: reject zero-length extension
elements in beacon IEs") made the extension ID itself safe to read, but
the fixed size structures behind these pointers are still unchecked.
Feeding an eight byte element stream ending in a zero length HT
capability element to nxpwifi_update_bss_desc_with_ie() and then copying
from bcn_ht_cap as the association path does gives, on a KASAN kernel:
BUG: KASAN: slab-out-of-bounds in nxpwifi_oob_repro+0x133/0x180
Read of size 1 at addr ffff88800716c308 by task swapper/0/1
CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.3.0-rc1 #1
Call Trace:
dump_stack_lvl+0x53/0x70
print_report+0xd0/0x630
kasan_report+0xce/0x100
nxpwifi_oob_repro+0x133/0x180
do_one_initcall+0x9a/0x2e0
kernel_init_freeable+0x294/0x460
With this patch the parser rejects the frame with -EINVAL, bcn_ht_cap
stays NULL, and KASAN is silent.
Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM (Claude Code)
Signed-off-by: Aamir Ahmed <elb12345@xxxxxxxxxxxxx>
---
Found by auditing the drivers merged this cycle.
nxpwifi_update_bss_desc_with_ie() already rejects short FH, DS, CF and
IBSS parameter sets and short vendor elements by length, but it stores
the HT, VHT and HE capability and operation pointers, the operating mode
notification and the 20/40 BSS coexistence pointer without the same
check. This patch adds the missing checks in the same style. I used the
sizeof(*ptr) form so a check cannot drift from the type the pointer is
later dereferenced as.
The equivalent parser in the sibling marvell/mwifiex driver
(mwifiex_update_bss_desc_with_ie()) has the same missing checks in
mainline today, so this is a driver-family pattern rather than an
nxpwifi-only slip; a matching mwifiex fix would be a separate change.
The cases guarded in the parser are HT/VHT/HE capability and operation,
dereferenced as fixed structs; the operating mode notification; and the
20/40 coexistence element, from which nxpwifi_cmd_append_11n_tlv()
copies one byte into the association request. The extended capabilities
consumer nxpwifi_is_ap_11ax_twt_supported() reads bcn_ext_cap->data[9]
at a fixed offset that has no single struct size, so it is guarded at
the consumer with an explicit datalen check rather than in the parser.
Reproducer: I built 7.3-rc1 with CONFIG_KASAN=y and CONFIG_NXPWIFI=y and
added a late_initcall to scan.c that kmemdups a short element stream
ending in a zero-length HT capability element into bss->beacon_buf,
calls nxpwifi_update_bss_desc_with_ie(), and copies sizeof(struct
ieee80211_ht_cap) from bcn_ht_cap as nxpwifi_cmd_append_11n_tlv() does.
Booted under QEMU the unpatched parser accepts the frame and the copy
trips the KASAN slab-out-of-bounds read quoted above; with this patch
the parser returns -EINVAL, bcn_ht_cap stays NULL and KASAN is silent.
The other element cases were confirmed by reading the same pattern in
the source rather than a separate splat. The harness is not part of this
patch; I can post it if useful.
The access point path is not affected: nxpwifi_uap_set_sta_ht_cap()
takes its HT capabilities from params->link_sta_params.ht_capa, which
nl80211 has already validated, rather than from a raw element stream.
I do not have IW61x hardware, so the full over-the-air association was
not exercised on a real device; the reproducer drives the parser and the
copy directly. nxpwifi is fullmac with no software-simulatable MAC, so a
parser-level reproducer is the strongest software evidence available.
The audit, the fix and this changelog were drafted with an LLM assistant
and reviewed by hand.
drivers/net/wireless/nxp/nxpwifi/11ax.c | 2 ++
drivers/net/wireless/nxp/nxpwifi/scan.c | 18 ++++++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c
index 96540914f3c..dc1e747383e 100644
--- a/drivers/net/wireless/nxp/nxpwifi/11ax.c
+++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c
@@ -402,6 +402,8 @@ static u8 nxpwifi_is_ap_11ax_twt_supported(struct nxpwifi_bssdescriptor *bss_des
return false;
ext_cap = (struct element *)bss_desc->bcn_ext_cap;
+ if (ext_cap->datalen < 10)
+ return false;
if (!(ext_cap->data[9] & WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT))
return false;
return true;
diff --git a/drivers/net/wireless/nxp/nxpwifi/scan.c b/drivers/net/wireless/nxp/nxpwifi/scan.c
index b77056983e8..d82ee8e502f 100644
--- a/drivers/net/wireless/nxp/nxpwifi/scan.c
+++ b/drivers/net/wireless/nxp/nxpwifi/scan.c
@@ -1209,6 +1209,8 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
(u16)(current_ptr - bss_entry->beacon_buf);
break;
case WLAN_EID_HT_CAPABILITY:
+ if (element_len < sizeof(*bss_entry->bcn_ht_cap))
+ return -EINVAL;
bss_entry->bcn_ht_cap =
(struct ieee80211_ht_cap *)(current_ptr +
elem_size);
@@ -1217,6 +1219,8 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
bss_entry->beacon_buf);
break;
case WLAN_EID_HT_OPERATION:
+ if (element_len < sizeof(*bss_entry->bcn_ht_oper))
+ return -EINVAL;
bss_entry->bcn_ht_oper =
(struct ieee80211_ht_operation *)(current_ptr +
elem_size);
@@ -1225,6 +1229,8 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
bss_entry->beacon_buf);
break;
case WLAN_EID_VHT_CAPABILITY:
+ if (element_len < sizeof(*bss_entry->bcn_vht_cap))
+ return -EINVAL;
bss_entry->disable_11ac = false;
bss_entry->bcn_vht_cap = (void *)(current_ptr +
elem_size);
@@ -1233,6 +1239,8 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
bss_entry->beacon_buf);
break;
case WLAN_EID_VHT_OPERATION:
+ if (element_len < sizeof(*bss_entry->bcn_vht_oper))
+ return -EINVAL;
bss_entry->bcn_vht_oper =
(void *)(current_ptr + elem_size);
bss_entry->vht_info_offset =
@@ -1240,6 +1248,8 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
bss_entry->beacon_buf);
break;
case WLAN_EID_BSS_COEX_2040:
+ if (!element_len)
+ return -EINVAL;
bss_entry->bcn_bss_co_2040 = current_ptr;
bss_entry->bss_co_2040_offset =
(u16)(current_ptr - bss_entry->beacon_buf);
@@ -1250,6 +1260,8 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
(u16)(current_ptr - bss_entry->beacon_buf);
break;
case WLAN_EID_OPMODE_NOTIF:
+ if (total_ie_len < sizeof(*bss_entry->oper_mode))
+ return -EINVAL;
bss_entry->oper_mode = (void *)current_ptr;
bss_entry->oper_mode_offset =
(u16)(current_ptr - bss_entry->beacon_buf);
@@ -1262,6 +1274,9 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
switch (elem->data[0]) {
case WLAN_EID_EXT_HE_CAPABILITY:
+ if (element_len <
+ 1 + sizeof(*bss_entry->bcn_he_cap))
+ return -EINVAL;
bss_entry->disable_11ax = false;
bss_entry->bcn_he_cap =
(void *)(current_ptr + elem_size + 1);
@@ -1270,6 +1285,9 @@ int nxpwifi_update_bss_desc_with_ie(struct nxpwifi_adapter *adapter,
bss_entry->beacon_buf);
break;
case WLAN_EID_EXT_HE_OPERATION:
+ if (element_len <
+ 1 + sizeof(*bss_entry->bcn_he_oper))
+ return -EINVAL;
bss_entry->bcn_he_oper =
(void *)(current_ptr + elem_size + 1);
bss_entry->he_info_offset =
base-commit: 654ae5d73c05bd2943d65636ce6cd0aa46e62f18
--
2.53.0.windows.1