mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 10:09:10 +02:00
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: 83e81961ff ("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>
This commit is contained in:
parent
10f9f426ac
commit
2e4edfa1e2
|
|
@ -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,
|
||||||
|
|
||||||
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,
|
||||||
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,
|
||||||
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,
|
||||||
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,
|
||||||
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,
|
||||||
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,
|
||||||
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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user