mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
parent
9cc144d95e
commit
760ac9d02d
|
|
@ -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. */
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user