Bluetooth: qca: add missing firmware sanity checks
authorJohan Hovold <johan+linaro@kernel.org>
Tue, 30 Apr 2024 17:07:39 +0000 (19:07 +0200)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Fri, 3 May 2024 17:05:29 +0000 (13:05 -0400)
Add the missing sanity checks when parsing the firmware files before
downloading them to avoid accessing and corrupting memory beyond the
vmalloced buffer.

Fixes: 83e81961ff7e ("Bluetooth: btqca: Introduce generic QCA ROME support")
Cc: stable@vger.kernel.org # 4.10
Signed-off-by: Johan Hovold <johan+linaro@kernel.org>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
drivers/bluetooth/btqca.c

index cfa71708397ba136a0e19b0ede7dc2789189200a..6743b0a79d7a5bf0dda6b34cc838e99091163cab 100644 (file)
@@ -268,9 +268,10 @@ int qca_send_pre_shutdown_cmd(struct hci_dev *hdev)
 }
 EXPORT_SYMBOL_GPL(qca_send_pre_shutdown_cmd);
 
-static void qca_tlv_check_data(struct hci_dev *hdev,
+static int qca_tlv_check_data(struct hci_dev *hdev,
                               struct qca_fw_config *config,
-               u8 *fw_data, enum qca_btsoc_type soc_type)
+                              u8 *fw_data, size_t fw_size,
+                              enum qca_btsoc_type soc_type)
 {
        const u8 *data;
        u32 type_len;
@@ -286,6 +287,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
 
        switch (config->type) {
        case ELF_TYPE_PATCH:
+               if (fw_size < 7)
+                       return -EINVAL;
+
                config->dnld_mode = QCA_SKIP_EVT_VSE_CC;
                config->dnld_type = QCA_SKIP_EVT_VSE_CC;
 
@@ -294,6 +298,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
                bt_dev_dbg(hdev, "File version      : 0x%x", fw_data[6]);
                break;
        case TLV_TYPE_PATCH:
+               if (fw_size < sizeof(struct tlv_type_hdr) + sizeof(struct tlv_type_patch))
+                       return -EINVAL;
+
                tlv = (struct tlv_type_hdr *)fw_data;
                type_len = le32_to_cpu(tlv->type_len);
                tlv_patch = (struct tlv_type_patch *)tlv->data;
@@ -333,6 +340,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
                break;
 
        case TLV_TYPE_NVM:
+               if (fw_size < sizeof(struct tlv_type_hdr))
+                       return -EINVAL;
+
                tlv = (struct tlv_type_hdr *)fw_data;
 
                type_len = le32_to_cpu(tlv->type_len);
@@ -341,17 +351,26 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
                BT_DBG("TLV Type\t\t : 0x%x", type_len & 0x000000ff);
                BT_DBG("Length\t\t : %d bytes", length);
 
+               if (fw_size < length + (tlv->data - fw_data))
+                       return -EINVAL;
+
                idx = 0;
                data = tlv->data;
-               while (idx < length) {
+               while (idx < length - sizeof(struct tlv_type_nvm)) {
                        tlv_nvm = (struct tlv_type_nvm *)(data + idx);
 
                        tag_id = le16_to_cpu(tlv_nvm->tag_id);
                        tag_len = le16_to_cpu(tlv_nvm->tag_len);
 
+                       if (length < idx + sizeof(struct tlv_type_nvm) + tag_len)
+                               return -EINVAL;
+
                        /* Update NVM tags as needed */
                        switch (tag_id) {
                        case EDL_TAG_ID_HCI:
+                               if (tag_len < 3)
+                                       return -EINVAL;
+
                                /* HCI transport layer parameters
                                 * enabling software inband sleep
                                 * onto controller side.
@@ -367,6 +386,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
                                break;
 
                        case EDL_TAG_ID_DEEP_SLEEP:
+                               if (tag_len < 1)
+                                       return -EINVAL;
+
                                /* Sleep enable mask
                                 * enabling deep sleep feature on controller.
                                 */
@@ -375,14 +397,16 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
                                break;
                        }
 
-                       idx += (sizeof(u16) + sizeof(u16) + 8 + tag_len);
+                       idx += sizeof(struct tlv_type_nvm) + tag_len;
                }
                break;
 
        default:
                BT_ERR("Unknown TLV type %d", config->type);
-               break;
+               return -EINVAL;
        }
+
+       return 0;
 }
 
 static int qca_tlv_send_segment(struct hci_dev *hdev, int seg_size,
@@ -532,7 +556,9 @@ static int qca_download_firmware(struct hci_dev *hdev,
        memcpy(data, fw->data, size);
        release_firmware(fw);
 
-       qca_tlv_check_data(hdev, config, data, soc_type);
+       ret = qca_tlv_check_data(hdev, config, data, size, soc_type);
+       if (ret)
+               return ret;
 
        segment = data;
        remain = size;