Bluetooth: btmtk: Validate the firmware layout before parsing it

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
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This commit is contained in:
Chris Lu 2026-09-09 20:00:09 +08:00 committed by Luiz Augusto von Dentz
parent 9cc144d95e
commit 760ac9d02d

View File

@ -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. */