[PATCH wireless-next 4/4] wifi: mm81x: bound the extended host table walk
From: Linmao Li
Date: Wed Aug 12 2026 - 02:08:34 EST
The extended host table is read from the chip into a buffer sized from
the length register, then walked without any of its contents being
checked:
- the length the table declares in its own header is used to compute
the end pointer, but is never compared against the length that was
read, so a larger value moves the end pointer past the buffer;
- the walk only requires a TLV header to start before that end, so a
header straddling it is read anyway;
- every recognised tag is cast to a structure larger than the TLV
header and read in full without checking that the TLV is that long,
so a short S1G capabilities, checksum or YAPS TLV at the end of the
table reads past the allocation.
Firmware that reports a table the driver does not agree with is enough
to reach these; it does not take a malicious device.
Reject a self-declared length that does not fit what was read, and walk
the TLVs by remaining length, skipping any TLV too short for the
structure its tag selects.
Fixes: b1906cea00b0 ("wifi: mm81x: add mm81x Wi-Fi HaLow driver")
Signed-off-by: Linmao Li <lilinmao@xxxxxxxxxx>
---
drivers/net/wireless/morsemicro/mm81x/fw.c | 47 +++++++++++++++-------
1 file changed, 32 insertions(+), 15 deletions(-)
diff --git a/drivers/net/wireless/morsemicro/mm81x/fw.c b/drivers/net/wireless/morsemicro/mm81x/fw.c
index d2fba42e53627..50fa9e4b5f803 100644
--- a/drivers/net/wireless/morsemicro/mm81x/fw.c
+++ b/drivers/net/wireless/morsemicro/mm81x/fw.c
@@ -503,6 +503,13 @@ static int mm81x_fw_read_ext_host_table(struct mm81x *mors,
if (ret)
goto exit;
+ /* The table describes its own length; it must fit what was read */
+ if (le32_to_cpu(host_tbl->ext_host_tbl_length) < sizeof(*host_tbl) ||
+ le32_to_cpu(host_tbl->ext_host_tbl_length) > ext_host_tbl_len) {
+ ret = -EINVAL;
+ goto exit;
+ }
+
mm81x_release_bus(mors);
*ext_host_table = host_tbl;
return ret;
@@ -570,34 +577,44 @@ int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)
end = ((u8 *)ext_host_table) +
le32_to_cpu(ext_host_table->ext_host_tbl_length);
- while (head < end) {
+ while (end - head >= (ptrdiff_t)sizeof(struct ext_host_tbl_tlv_hdr)) {
struct ext_host_tbl_tlv_hdr *hdr =
(struct ext_host_tbl_tlv_hdr *)head;
+ u16 tlv_len = le16_to_cpu(hdr->length);
+
+ if (tlv_len < sizeof(*hdr) || tlv_len > end - head)
+ break;
switch (le16_to_cpu(hdr->tag)) {
- case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:
- mm81x_fw_update_capabilities(
- mors, (struct ext_host_tbl_s1g_caps *)hdr);
+ case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES: {
+ struct ext_host_tbl_s1g_caps *caps = (void *)hdr;
+
+ if (tlv_len >= sizeof(*caps))
+ mm81x_fw_update_capabilities(mors, caps);
break;
+ }
+ case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM: {
+ struct ext_host_tbl_insert_skb_checksum *csum =
+ (void *)hdr;
- case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
- mm81x_fw_update_validate_skb_checksum(
- mors,
- (struct ext_host_tbl_insert_skb_checksum *)hdr);
+ if (tlv_len >= sizeof(*csum))
+ mm81x_fw_update_validate_skb_checksum(mors,
+ csum);
break;
+ }
+ case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE: {
+ struct ext_host_tbl_yaps_table *yaps = (void *)hdr;
- case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
- mm81x_yaps_hw_read_table(
- mors, &((struct ext_host_tbl_yaps_table *)hdr)
- ->yaps_table);
+ if (tlv_len >= sizeof(*yaps))
+ mm81x_yaps_hw_read_table(mors,
+ &yaps->yaps_table);
break;
+ }
default:
break;
}
- head += le16_to_cpu(hdr->length);
- if (!hdr->length)
- break;
+ head += tlv_len;
}
kfree(ext_host_table);
--
2.25.1