[PATCH 1/3] Bluetooth: btmtk: Validate the firmware layout before parsing it
From: Chris Lu <hidden>
Date: 2026-09-09 12:00:38
Also in:
linux-mediatek, lkml
Subsystem:
bluetooth drivers, the rest · Maintainers:
Marcel Holtmann, Luiz Augusto von Dentz, Linus Torvalds
btmtk_setup_firmware_79xx() reads the section count out of the firmware's global descriptor, then walks a 64-byte section map per section and hands each section's file-supplied offset and length to the WMT download path. None of that is checked against fw->size, so a truncated or corrupted file makes the driver read past the end of request_firmware()'s buffer. On 32-bit the section count can also wrap the section map size calculation. Add btmtk_fw_validate_layout() to check that the header, global descriptor and section map array all fit, and btmtk_fw_validate_section() to check one section's offset and length, both written so the size arithmetic cannot wrap. A section count of zero is rejected as well: it passes every size check but leaves the download loop with nothing to do, so the function would report success on hardware it never programmed. They are helpers rather than open-coded because the MT7928 CBMCU download path added later needs the same arithmetic. This is hardening against a corrupted /lib/firmware rather than a bug an unprivileged user can trigger, hence bluetooth-next and no Fixes: tag. Well-formed firmware files are unaffected. Signed-off-by: Chris Lu <redacted> Assisted-by: Claude:claude-opus-5 --- drivers/bluetooth/btmtk.c | 74 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-)
diff --git a/drivers/bluetooth/btmtk.c b/drivers/bluetooth/btmtk.c
index 911aba5c134e..07a7a7a3656a 100644
--- a/drivers/bluetooth/btmtk.c
+++ b/drivers/bluetooth/btmtk.c@@ -6,6 +6,7 @@ #include <linux/firmware.h> #include <linux/usb.h> #include <linux/iopoll.h> +#include <linux/overflow.h> #include <linux/unaligned.h> #include <net/bluetooth/bluetooth.h>
@@ -59,6 +60,67 @@ struct btmtk_section_map { }; } __packed; +/* Reject a firmware image whose header, global descriptor or section map + * array does not fit in the file, and hand back the validated section + * count, so that the section map walks stay inside the firmware buffer. + */ +static int btmtk_fw_validate_layout(struct hci_dev *hdev, + const struct firmware *fw, + u32 *section_num) +{ + const struct btmtk_global_desc *globaldesc; + size_t need; + + if (fw->size < MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE) { + bt_dev_err(hdev, "Firmware too small: %zu bytes, need at least %u", + fw->size, + MTK_FW_ROM_PATCH_HEADER_SIZE + MTK_FW_ROM_PATCH_GD_SIZE); + return -EINVAL; + } + + globaldesc = (const struct btmtk_global_desc *)(fw->data + + MTK_FW_ROM_PATCH_HEADER_SIZE); + *section_num = le32_to_cpu(globaldesc->section_num); + + if (!*section_num) { + bt_dev_err(hdev, "Firmware declares no sections"); + return -EINVAL; + } + + if (check_mul_overflow((size_t)MTK_FW_ROM_PATCH_SEC_MAP_SIZE, + (size_t)*section_num, &need) || + check_add_overflow(need, + (size_t)(MTK_FW_ROM_PATCH_HEADER_SIZE + + MTK_FW_ROM_PATCH_GD_SIZE), + &need)) { + bt_dev_err(hdev, "Firmware section count too large: %u", + *section_num); + return -EINVAL; + } + + if (fw->size < need) { + bt_dev_err(hdev, "Firmware truncated: %zu bytes, need %zu for %u sections", + fw->size, need, *section_num); + return -EINVAL; + } + + return 0; +} + +/* Reject a section whose payload does not lie within the firmware file. */ +static int btmtk_fw_validate_section(struct hci_dev *hdev, + const struct firmware *fw, int index, + u32 offset, u32 size) +{ + if (offset > fw->size || size > fw->size - offset) { + bt_dev_err(hdev, "Firmware section %d out of bounds: offset=%u, size=%u, fw_size=%zu", + index, offset, size, fw->size); + return -EINVAL; + } + + return 0; +} + static void btmtk_coredump(struct hci_dev *hdev) { int err;
@@ -137,7 +199,6 @@ int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname, { struct btmtk_hci_wmt_params wmt_params; struct btmtk_patch_header *hdr; - struct btmtk_global_desc *globaldesc = NULL; struct btmtk_section_map *sectionmap; const struct firmware *fw; const u8 *fw_ptr;
@@ -153,11 +214,13 @@ int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname, return err; } + err = btmtk_fw_validate_layout(hdev, fw, §ion_num); + if (err < 0) + goto err_release_fw; + fw_ptr = fw->data; fw_bin_ptr = fw_ptr; hdr = (struct btmtk_patch_header *)fw_ptr; - globaldesc = (struct btmtk_global_desc *)(fw_ptr + MTK_FW_ROM_PATCH_HEADER_SIZE); - section_num = le32_to_cpu(globaldesc->section_num); bt_dev_info(hdev, "HW/SW Version: 0x%04x%04x, Build Time: %s", le16_to_cpu(hdr->hwver), le16_to_cpu(hdr->swver), hdr->datetime);
@@ -180,6 +243,11 @@ int btmtk_setup_firmware_79xx(struct hci_dev *hdev, const char *fwname, continue; if (dl_size > 0) { + err = btmtk_fw_validate_section(hdev, fw, i, + section_offset, dl_size); + if (err < 0) + goto err_release_fw; + retry = 20; while (retry > 0) { cmd[0] = 0; /* 0 means legacy dl mode. */
--
2.45.2