* [PATCH 1/3] wifi: mt76: mt7921: validate NIC capability payload lengths
2026-10-28 5:07 [PATCH 0/3] wifi: mt76: validate mt792x firmware TLV payloads Jiale Yao
@ 2026-10-28 5:07 ` Jiale Yao
2026-10-28 5:07 ` [PATCH 2/3] wifi: mt76: mt7925: validate debug message lengths Jiale Yao
2026-10-28 5:07 ` [PATCH 3/3] wifi: mt76: mt7925: validate NIC capability payload lengths Jiale Yao
2 siblings, 0 replies; 4+ messages in thread
From: Jiale Yao @ 2026-10-28 5:07 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Deren Wu, linux-wireless, linux-kernel, linux-arm-kernel,
linux-mediatek
Cc: stable
The NIC capability parser checks that each declared payload fits in the
skb, but it does not ensure that the payload is large enough for the
selected capability. A truncated firmware response can therefore make
the driver read beyond the current TLV while copying fixed-size fields or
parsing capability structures.
Validate every consumed payload before accessing it and reject the
response when a known capability is shorter than its fixed data.
Fixes: dab35009fc1c ("wifi: mt76: mt7921: move connac nic capability handling to mt7921")
Cc: stable@vger.kernel.org
Signed-off-by: Jiale Yao <yaojiale02@163.com>
---
.../net/wireless/mediatek/mt76/mt7921/mcu.c | 41 +++++++++++++++----
1 file changed, 33 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
index 25638c68c468..c960648b2b61 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c
@@ -521,8 +521,8 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name)
return ret;
}
-static void mt7921_mcu_parse_tx_resource(struct mt76_dev *dev,
- struct sk_buff *skb)
+static bool mt7921_mcu_parse_tx_resource(struct mt76_dev *dev,
+ struct sk_buff *skb, int len)
{
struct mt76_sdio *sdio = &dev->sdio;
struct mt7921_tx_resource {
@@ -537,6 +537,9 @@ static void mt7921_mcu_parse_tx_resource(struct mt76_dev *dev,
u8 pad[3];
} __packed * tx_res;
+ if (len < sizeof(*tx_res))
+ return false;
+
tx_res = (struct mt7921_tx_resource *)skb->data;
sdio->sched.pse_data_quota = le32_to_cpu(tx_res->pse_data_quota);
sdio->pse_mcu_quota_max = le32_to_cpu(tx_res->pse_mcu_quota);
@@ -546,10 +549,12 @@ static void mt7921_mcu_parse_tx_resource(struct mt76_dev *dev,
sdio->sched.ple_data_quota = le32_to_cpu(tx_res->ple_data_quota);
sdio->sched.pse_page_size = le16_to_cpu(tx_res->pse_page_size);
sdio->sched.deficit = tx_res->pp_padding;
+
+ return true;
}
-static void mt7921_mcu_parse_phy_cap(struct mt76_dev *dev,
- struct sk_buff *skb)
+static bool mt7921_mcu_parse_phy_cap(struct mt76_dev *dev,
+ struct sk_buff *skb, int len)
{
struct mt7921_phy_cap {
u8 ht;
@@ -571,12 +576,17 @@ static void mt7921_mcu_parse_phy_cap(struct mt76_dev *dev,
WF0_5G
};
+ if (len < sizeof(*cap))
+ return false;
+
cap = (struct mt7921_phy_cap *)skb->data;
dev->phy.antenna_mask = BIT(cap->nss) - 1;
dev->phy.chainmask = dev->phy.antenna_mask;
dev->phy.cap.has_2ghz = cap->hw_path & BIT(WF0_24G);
dev->phy.cap.has_5ghz = cap->hw_path & BIT(WF0_5G);
+
+ return true;
}
static int mt7921_mcu_get_nic_capability(struct mt792x_phy *mphy)
@@ -620,20 +630,30 @@ static int mt7921_mcu_get_nic_capability(struct mt792x_phy *mphy)
switch (le32_to_cpu(tlv->type)) {
case MT_NIC_CAP_6G:
+ if (len < sizeof(skb->data[0]))
+ goto invalid;
+
phy->cap.has_6ghz = skb->data[0];
break;
case MT_NIC_CAP_MAC_ADDR:
+ if (len < ETH_ALEN)
+ goto invalid;
+
memcpy(phy->macaddr, (void *)skb->data, ETH_ALEN);
break;
case MT_NIC_CAP_PHY:
- mt7921_mcu_parse_phy_cap(phy->dev, skb);
+ if (!mt7921_mcu_parse_phy_cap(phy->dev, skb, len))
+ goto invalid;
break;
case MT_NIC_CAP_TX_RESOURCE:
- if (mt76_is_sdio(phy->dev))
- mt7921_mcu_parse_tx_resource(phy->dev,
- skb);
+ if (mt76_is_sdio(phy->dev) &&
+ !mt7921_mcu_parse_tx_resource(phy->dev, skb, len))
+ goto invalid;
break;
case MT_NIC_CAP_CHIP_CAP:
+ if (len < sizeof(mphy->chip_cap))
+ goto invalid;
+
memcpy(&mphy->chip_cap, (void *)skb->data, sizeof(u64));
break;
default:
@@ -641,6 +661,11 @@ static int mt7921_mcu_get_nic_capability(struct mt792x_phy *mphy)
}
skb_pull(skb, len);
}
+
+ goto out;
+
+invalid:
+ ret = -EINVAL;
out:
dev_kfree_skb(skb);
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 2/3] wifi: mt76: mt7925: validate debug message lengths
2026-10-28 5:07 [PATCH 0/3] wifi: mt76: validate mt792x firmware TLV payloads Jiale Yao
2026-10-28 5:07 ` [PATCH 1/3] wifi: mt76: mt7921: validate NIC capability payload lengths Jiale Yao
@ 2026-10-28 5:07 ` Jiale Yao
2026-10-28 5:07 ` [PATCH 3/3] wifi: mt76: mt7925: validate NIC capability payload lengths Jiale Yao
2 siblings, 0 replies; 4+ messages in thread
From: Jiale Yao @ 2026-10-28 5:07 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Mingyen Hsieh, Quan Zhou, Deren Wu, Leon Yen, linux-wireless,
linux-kernel, linux-arm-kernel, linux-mediatek
Cc: stable
The debug message event handler removes the event prefix and accesses the
message header without checking the skb length. It also subtracts the
fixed header from the firmware-provided length without checking for an
underflow and trusts the string length. A truncated event can therefore
cause out-of-bounds reads while formatting firmware logs.
Validate the event prefix, fixed fields, declared message extent and each
format-specific payload before reading them.
Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips")
Cc: stable@vger.kernel.org
Signed-off-by: Jiale Yao <yaojiale02@163.com>
---
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 24 +++++++++++++++++--
1 file changed, 22 insertions(+), 2 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 26cdb3f0a98a..c612fda55eb7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -596,8 +596,14 @@ mt7925_mcu_uni_debug_msg_event(struct mt792x_dev *dev, struct sk_buff *skb)
} __packed txt;
};
} __packed * hdr;
+ u16 msg_len;
+ if (skb->len < sizeof(struct mt7925_mcu_rxd) + 4)
+ return;
skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4);
+ if (skb->len < offsetof(struct mt7925_uni_debug_msg, idx))
+ return;
+
hdr = (struct mt7925_uni_debug_msg *)skb->data;
if (hdr->id == 0x28) {
@@ -608,19 +614,28 @@ mt7925_mcu_uni_debug_msg_event(struct mt792x_dev *dev, struct sk_buff *skb)
return;
}
+ msg_len = le16_to_cpu(hdr->len);
+ if (msg_len < offsetof(struct mt7925_uni_debug_msg, idx) ||
+ msg_len > skb->len)
+ return;
+
if (hdr->type == 0) { /* idx log */
int i, ret, len = PAGE_SIZE - 1, nr_val;
- struct page *page = dev_alloc_pages(get_order(len));
+ struct page *page;
__le32 *val;
char *buf, *cur;
+ if (msg_len < sizeof(*hdr))
+ return;
+
+ page = dev_alloc_pages(get_order(len));
if (!page)
return;
buf = page_address(page);
cur = buf;
- nr_val = (le16_to_cpu(hdr->len) - sizeof(*hdr)) / 4;
+ nr_val = (msg_len - sizeof(*hdr)) / sizeof(*val);
val = (__le32 *)hdr->idx.data;
for (i = 0; i < nr_val && len > 0; i++) {
ret = snprintf(cur, len, "0x%x,", le32_to_cpu(val[i]));
@@ -635,6 +650,11 @@ mt7925_mcu_uni_debug_msg_event(struct mt792x_dev *dev, struct sk_buff *skb)
le32_to_cpu(hdr->idx.idx), nr_val, buf);
put_page(page);
} else if (hdr->type == 2) { /* str log */
+ if (msg_len < offsetof(struct mt7925_uni_debug_msg, txt.data) ||
+ hdr->txt.len > msg_len - offsetof(struct mt7925_uni_debug_msg,
+ txt.data))
+ return;
+
wiphy_info(mt76_hw(dev)->wiphy, "%.*s", hdr->txt.len, hdr->txt.data);
}
}
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] wifi: mt76: mt7925: validate NIC capability payload lengths
2026-10-28 5:07 [PATCH 0/3] wifi: mt76: validate mt792x firmware TLV payloads Jiale Yao
2026-10-28 5:07 ` [PATCH 1/3] wifi: mt76: mt7921: validate NIC capability payload lengths Jiale Yao
2026-10-28 5:07 ` [PATCH 2/3] wifi: mt76: mt7925: validate debug message lengths Jiale Yao
@ 2026-10-28 5:07 ` Jiale Yao
2 siblings, 0 replies; 4+ messages in thread
From: Jiale Yao @ 2026-10-28 5:07 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Leon Yen, Quan Zhou, Rong Yan, Hao Zhang, linux-wireless,
linux-kernel, linux-arm-kernel, linux-mediatek
Cc: stable
The NIC capability parser only checks whether a declared TLV length fits
in the skb. It does not reject lengths shorter than the TLV header or
ensure that known payloads contain the fields consumed by the driver. A
truncated firmware response can therefore stall the parser on a zero
length TLV or cause out-of-bounds reads from fixed-size payloads.
Reject undersized TLVs and validate every known payload before accessing
it.
Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips")
Cc: stable@vger.kernel.org
Signed-off-by: Jiale Yao <yaojiale02@163.com>
---
.../net/wireless/mediatek/mt76/mt7925/mcu.c | 43 +++++++++++++++----
1 file changed, 35 insertions(+), 8 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index c612fda55eb7..41138203ada2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1039,8 +1039,8 @@ int mt7925_mcu_get_temperature(struct mt792x_phy *phy)
return temperature;
}
-static void
-mt7925_mcu_parse_phy_cap(struct mt792x_dev *dev, char *data)
+static bool
+mt7925_mcu_parse_phy_cap(struct mt792x_dev *dev, char *data, int len)
{
struct mt76_phy *mphy = &dev->mt76.phy;
struct mt76_dev *mdev = mphy->dev;
@@ -1064,16 +1064,21 @@ mt7925_mcu_parse_phy_cap(struct mt792x_dev *dev, char *data)
WF0_5G
};
+ if (len < sizeof(*cap))
+ return false;
+
cap = (struct mt7925_mcu_phy_cap *)data;
mdev->phy.antenna_mask = BIT(cap->nss) - 1;
mdev->phy.chainmask = mdev->phy.antenna_mask;
mdev->phy.cap.has_2ghz = cap->hw_path & BIT(WF0_24G);
mdev->phy.cap.has_5ghz = cap->hw_path & BIT(WF0_5G);
+
+ return true;
}
-static void
-mt7925_mcu_parse_eml_cap(struct mt792x_dev *dev, char *data)
+static bool
+mt7925_mcu_parse_eml_cap(struct mt792x_dev *dev, char *data, int len)
{
struct mt7925_mcu_eml_cap {
u8 rsv[4];
@@ -1081,9 +1086,14 @@ mt7925_mcu_parse_eml_cap(struct mt792x_dev *dev, char *data)
u8 rsv2[6];
} __packed * cap;
+ if (len < sizeof(*cap))
+ return false;
+
cap = (struct mt7925_mcu_eml_cap *)data;
dev->phy.eml_cap = le16_to_cpu(cap->eml_cap);
+
+ return true;
}
static int
@@ -1121,36 +1131,53 @@ mt7925_mcu_get_nic_capability(struct mt792x_dev *dev)
for (i = 0; i < le16_to_cpu(hdr->n_element); i++) {
struct tlv *tlv = (struct tlv *)skb->data;
- int len;
+ int data_len, len;
if (skb->len < sizeof(*tlv))
break;
len = le16_to_cpu(tlv->len);
- if (skb->len < len)
+ if (len < sizeof(*tlv) || skb->len < len)
break;
+ data_len = len - sizeof(*tlv);
switch (le16_to_cpu(tlv->tag)) {
case MT_NIC_CAP_6G:
+ if (len < sizeof(*tlv) + sizeof(tlv->data[0]))
+ goto invalid;
+
mphy->cap.has_6ghz = !!tlv->data[0];
break;
case MT_NIC_CAP_MAC_ADDR:
+ if (len < sizeof(*tlv) + ETH_ALEN)
+ goto invalid;
+
memcpy(mphy->macaddr, (void *)tlv->data, ETH_ALEN);
break;
case MT_NIC_CAP_PHY:
- mt7925_mcu_parse_phy_cap(dev, tlv->data);
+ if (!mt7925_mcu_parse_phy_cap(dev, tlv->data, data_len))
+ goto invalid;
break;
case MT_NIC_CAP_CHIP_CAP:
+ if (len < sizeof(*tlv) + sizeof(__le64))
+ goto invalid;
+
dev->phy.chip_cap = le64_to_cpu(*(__le64 *)tlv->data);
break;
case MT_NIC_CAP_EML_CAP:
- mt7925_mcu_parse_eml_cap(dev, tlv->data);
+ if (!mt7925_mcu_parse_eml_cap(dev, tlv->data, data_len))
+ goto invalid;
break;
default:
break;
}
skb_pull(skb, len);
}
+
+ goto out;
+
+invalid:
+ ret = -EINVAL;
out:
dev_kfree_skb(skb);
return ret;
--
2.34.1
^ permalink raw reply [flat|nested] 4+ messages in thread