[PATCH 1/3] Bluetooth: btmtk: Validate the firmware layout before parsing it

From: Chris Lu

Date: Wed Sep 09 2026 - 08:52:37 EST


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 <chris.lu@xxxxxxxxxxxx>
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, &section_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