Commit 2e4edfa1 authored by Johan Hovold's avatar Johan Hovold Committed by Luiz Augusto von Dentz

Bluetooth: qca: add missing firmware sanity checks

Add the missing sanity checks when parsing the firmware files before
downloading them to avoid accessing and corrupting memory beyond the
vmalloced buffer.

Fixes: 83e81961 ("Bluetooth: btqca: Introduce generic QCA ROME support")
Cc: stable@vger.kernel.org	# 4.10
Signed-off-by: default avatarJohan Hovold <johan+linaro@kernel.org>
Signed-off-by: default avatarLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
parent 10f9f426
...@@ -268,9 +268,10 @@ int qca_send_pre_shutdown_cmd(struct hci_dev *hdev) ...@@ -268,9 +268,10 @@ int qca_send_pre_shutdown_cmd(struct hci_dev *hdev)
} }
EXPORT_SYMBOL_GPL(qca_send_pre_shutdown_cmd); 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, 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; const u8 *data;
u32 type_len; u32 type_len;
...@@ -286,6 +287,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev, ...@@ -286,6 +287,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
switch (config->type) { switch (config->type) {
case ELF_TYPE_PATCH: case ELF_TYPE_PATCH:
if (fw_size < 7)
return -EINVAL;
config->dnld_mode = QCA_SKIP_EVT_VSE_CC; config->dnld_mode = QCA_SKIP_EVT_VSE_CC;
config->dnld_type = 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, ...@@ -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]); bt_dev_dbg(hdev, "File version : 0x%x", fw_data[6]);
break; break;
case TLV_TYPE_PATCH: 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; tlv = (struct tlv_type_hdr *)fw_data;
type_len = le32_to_cpu(tlv->type_len); type_len = le32_to_cpu(tlv->type_len);
tlv_patch = (struct tlv_type_patch *)tlv->data; tlv_patch = (struct tlv_type_patch *)tlv->data;
...@@ -333,6 +340,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev, ...@@ -333,6 +340,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
break; break;
case TLV_TYPE_NVM: case TLV_TYPE_NVM:
if (fw_size < sizeof(struct tlv_type_hdr))
return -EINVAL;
tlv = (struct tlv_type_hdr *)fw_data; tlv = (struct tlv_type_hdr *)fw_data;
type_len = le32_to_cpu(tlv->type_len); type_len = le32_to_cpu(tlv->type_len);
...@@ -341,17 +351,26 @@ static void qca_tlv_check_data(struct hci_dev *hdev, ...@@ -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("TLV Type\t\t : 0x%x", type_len & 0x000000ff);
BT_DBG("Length\t\t : %d bytes", length); BT_DBG("Length\t\t : %d bytes", length);
if (fw_size < length + (tlv->data - fw_data))
return -EINVAL;
idx = 0; idx = 0;
data = tlv->data; data = tlv->data;
while (idx < length) { while (idx < length - sizeof(struct tlv_type_nvm)) {
tlv_nvm = (struct tlv_type_nvm *)(data + idx); tlv_nvm = (struct tlv_type_nvm *)(data + idx);
tag_id = le16_to_cpu(tlv_nvm->tag_id); tag_id = le16_to_cpu(tlv_nvm->tag_id);
tag_len = le16_to_cpu(tlv_nvm->tag_len); 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 */ /* Update NVM tags as needed */
switch (tag_id) { switch (tag_id) {
case EDL_TAG_ID_HCI: case EDL_TAG_ID_HCI:
if (tag_len < 3)
return -EINVAL;
/* HCI transport layer parameters /* HCI transport layer parameters
* enabling software inband sleep * enabling software inband sleep
* onto controller side. * onto controller side.
...@@ -367,6 +386,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev, ...@@ -367,6 +386,9 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
break; break;
case EDL_TAG_ID_DEEP_SLEEP: case EDL_TAG_ID_DEEP_SLEEP:
if (tag_len < 1)
return -EINVAL;
/* Sleep enable mask /* Sleep enable mask
* enabling deep sleep feature on controller. * enabling deep sleep feature on controller.
*/ */
...@@ -375,14 +397,16 @@ static void qca_tlv_check_data(struct hci_dev *hdev, ...@@ -375,14 +397,16 @@ static void qca_tlv_check_data(struct hci_dev *hdev,
break; break;
} }
idx += (sizeof(u16) + sizeof(u16) + 8 + tag_len); idx += sizeof(struct tlv_type_nvm) + tag_len;
} }
break; break;
default: default:
BT_ERR("Unknown TLV type %d", config->type); 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, 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, ...@@ -532,7 +556,9 @@ static int qca_download_firmware(struct hci_dev *hdev,
memcpy(data, fw->data, size); memcpy(data, fw->data, size);
release_firmware(fw); 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; segment = data;
remain = size; remain = size;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment