[PATCH] wifi: nxpwifi: bound the TLV lengths in the 11AX_CFG response

From: Yehyeong Lee

Date: Thu Oct 15 2026 - 21:50:59 EST


nxpwifi_ret_11ax_cfg() walks the TLVs the firmware returns for
HOST_CMD_11AX_CFG and copies one of them into two fixed-size
destinations. Nothing bounds any of the three lengths involved.

The walk length comes straight out of the response:

left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN;

left_len is a u16 and the two sizes add up to 11, so a response claiming
fewer than 11 bytes makes the subtraction wrap instead of ending the
walk, and the loop condition is only "left_len > sizeof(*header)".

Inside the loop, header->len is used as a memcpy length twice without
being compared to what is left or to what the destinations hold:

memcpy((u8 *)&he_cap->ext_id,
(u8 *)header + sizeof(*header) + 1,
tlv_len);
memcpy(priv->user_he_cap, (u8 *)header, sizeof(*header) + tlv_len);

The first destination runs from ext_id to the end of struct
nxpwifi_11ax_he_cap_cfg, which is 50 bytes; the second is
priv->user_he_cap[HE_CAP_MAX_SIZE], which is 54. header->len is 16 bits.
nxpwifi_set_11ax_status() declares the struct nxpwifi_11ax_he_cfg this
writes into on the stack and issues the command synchronously, so the
frame is still live when the response is parsed.

Do what the driver already does elsewhere. nxpwifi_ret_get_hw_spec()
checks that the response is longer than its fixed part before
subtracting, and nxpwifi_update_vs_ie() rejects a token longer than the
bytes remaining. Add the matching three checks here: refuse a response
that cannot hold the fixed part, refuse a TLV that claims more than the
remaining length, and refuse an HE-capability TLV that does not fit
either destination. A TLV whose length is within those bounds is copied
exactly as before, including one that fills both destinations completely.

This is not a claim of a reachable out-of-bounds write. The lengths come
from the device firmware, and whether it can emit these values is not
something the tree answers. The arithmetic reaches past both
destinations, the idiom is already in the driver, and the checks are
cheap.

The driver has not appeared in a released kernel, so there is nothing to
backport to and no stable tag.

Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x")
Signed-off-by: Yehyeong Lee <yhlee@xxxxxxxxxxxxxxxxxx>
---
drivers/net/wireless/nxp/nxpwifi/11ax.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/nxp/nxpwifi/11ax.c b/drivers/net/wireless/nxp/nxpwifi/11ax.c
index 96540914f3cf6..7e05dd5ccaa81 100644
--- a/drivers/net/wireless/nxp/nxpwifi/11ax.c
+++ b/drivers/net/wireless/nxp/nxpwifi/11ax.c
@@ -204,20 +204,36 @@ int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv,
{
struct host_cmd_11ax_cfg *he_cfg = &resp->params.ax_cfg;
struct nxpwifi_ie_types_header *header;
- u16 left_len, tlv_type, tlv_len;
+ u16 resp_size, left_len, tlv_type, tlv_len;
u8 ext_id;
struct nxpwifi_11ax_he_cap_cfg *he_cap = &ax_cfg->he_cap_cfg;

- left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN;
+ resp_size = le16_to_cpu(resp->size);
+ if (resp_size < S_DS_GEN + sizeof(*he_cfg))
+ return -EINVAL;
+
+ left_len = resp_size - S_DS_GEN - sizeof(*he_cfg);
header = (struct nxpwifi_ie_types_header *)he_cfg->tlv;

while (left_len > sizeof(*header)) {
tlv_type = le16_to_cpu(header->type);
tlv_len = le16_to_cpu(header->len);

+ if (sizeof(*header) + tlv_len > left_len)
+ return -EINVAL;
+
if (tlv_type == TLV_TYPE_EXTENSION_ID) {
ext_id = *((u8 *)header + sizeof(*header) + 1);
if (ext_id == WLAN_EID_EXT_HE_CAPABILITY) {
+ if (tlv_len > sizeof(*he_cap) -
+ offsetof(struct nxpwifi_11ax_he_cap_cfg,
+ ext_id) ||
+ sizeof(*header) + tlv_len >
+ sizeof(priv->user_he_cap) ||
+ sizeof(*header) + tlv_len >
+ sizeof(priv->user_2g_he_cap))
+ return -EINVAL;
+
he_cap->id = tlv_type;
he_cap->len = tlv_len;
memcpy((u8 *)&he_cap->ext_id,

base-commit: 4056b3c9d3c92ea45955641dbafeb9b960e146ef
--
2.43.0