From: Chris Lu <chris.lu@mediatek.com>
To: Marcel Holtmann <marcel@holtmann.org>,
Johan Hedberg <johan.hedberg@gmail.com>,
Luiz Von Dentz <luiz.dentz@gmail.com>
Cc: Sean Wang <sean.wang@mediatek.com>,
Will Lee <will-cy.Lee@mediatek.com>, SS Wu <ss.wu@mediatek.com>,
linux-bluetooth <linux-bluetooth@vger.kernel.org>,
linux-kernel <linux-kernel@vger.kernel.org>,
linux-mediatek <linux-mediatek@lists.infradead.org>,
Chris Lu <chris.lu@mediatek.com>
Subject: [PATCH 1/3] Bluetooth: btmtk: Validate the firmware layout before parsing it
Date: Wed, 9 Sep 2026 20:00:09 +0800 [thread overview]
Message-ID: <20260909120011.1198001-2-chris.lu@mediatek.com> (raw)
In-Reply-To: <20260909120011.1198001-1-chris.lu@mediatek.com>
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@mediatek.com>
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
next prev parent reply other threads:[~2026-09-09 12:00 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 12:00 [PATCH 0/3] Bluetooth: btmtk: Harden firmware parsing and improve logging Chris Lu
2026-09-09 12:00 ` Chris Lu [this message]
2026-09-09 15:54 ` bluez.test.bot
2026-09-09 12:00 ` [PATCH 2/3] Bluetooth: btmtk: Improve BT firmware logging Chris Lu
2026-09-09 12:00 ` [PATCH 3/3] Bluetooth: btmtk: Replace magic numbers with WMT packet flag enum Chris Lu
2026-09-09 20:00 ` [PATCH 0/3] Bluetooth: btmtk: Harden firmware parsing and improve logging patchwork-bot+bluetooth
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260909120011.1198001-2-chris.lu@mediatek.com \
--to=chris.lu@mediatek.com \
--cc=johan.hedberg@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mediatek@lists.infradead.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=sean.wang@mediatek.com \
--cc=ss.wu@mediatek.com \
--cc=will-cy.Lee@mediatek.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.