| From 2c840e2ef65e2d88761d4d6f92ac40690f02049e Mon Sep 17 00:00:00 2001 |
| From: Sasha Levin <sashal@kernel.org> |
| Date: Thu, 8 May 2025 22:15:20 +0800 |
| Subject: Bluetooth: btusb: use skb_pull to avoid unsafe access in QCA dump |
| handling |
| |
| From: En-Wei Wu <en-wei.wu@canonical.com> |
| |
| [ Upstream commit 4bcb0c7dc25446b99fc7a8fa2a143d69f3314162 ] |
| |
| Use skb_pull() and skb_pull_data() to safely parse QCA dump packets. |
| |
| This avoids direct pointer math on skb->data, which could lead to |
| invalid access if the packet is shorter than expected. |
| |
| Fixes: 20981ce2d5a5 ("Bluetooth: btusb: Add WCN6855 devcoredump support") |
| Signed-off-by: En-Wei Wu <en-wei.wu@canonical.com> |
| Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/bluetooth/btusb.c | 98 ++++++++++++++++----------------------- |
| 1 file changed, 40 insertions(+), 58 deletions(-) |
| |
| diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c |
| index ccd0a21da3955..b15f3ed767c53 100644 |
| --- a/drivers/bluetooth/btusb.c |
| +++ b/drivers/bluetooth/btusb.c |
| @@ -3014,9 +3014,8 @@ static void btusb_coredump_qca(struct hci_dev *hdev) |
| static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| { |
| int ret = 0; |
| + unsigned int skip = 0; |
| u8 pkt_type; |
| - u8 *sk_ptr; |
| - unsigned int sk_len; |
| u16 seqno; |
| u32 dump_size; |
| |
| @@ -3025,18 +3024,13 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| struct usb_device *udev = btdata->udev; |
| |
| pkt_type = hci_skb_pkt_type(skb); |
| - sk_ptr = skb->data; |
| - sk_len = skb->len; |
| + skip = sizeof(struct hci_event_hdr); |
| + if (pkt_type == HCI_ACLDATA_PKT) |
| + skip += sizeof(struct hci_acl_hdr); |
| |
| - if (pkt_type == HCI_ACLDATA_PKT) { |
| - sk_ptr += HCI_ACL_HDR_SIZE; |
| - sk_len -= HCI_ACL_HDR_SIZE; |
| - } |
| - |
| - sk_ptr += HCI_EVENT_HDR_SIZE; |
| - sk_len -= HCI_EVENT_HDR_SIZE; |
| + skb_pull(skb, skip); |
| + dump_hdr = (struct qca_dump_hdr *)skb->data; |
| |
| - dump_hdr = (struct qca_dump_hdr *)sk_ptr; |
| seqno = le16_to_cpu(dump_hdr->seqno); |
| if (seqno == 0) { |
| set_bit(BTUSB_HW_SSR_ACTIVE, &btdata->flags); |
| @@ -3056,16 +3050,15 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| |
| btdata->qca_dump.ram_dump_size = dump_size; |
| btdata->qca_dump.ram_dump_seqno = 0; |
| - sk_ptr += offsetof(struct qca_dump_hdr, data0); |
| - sk_len -= offsetof(struct qca_dump_hdr, data0); |
| + |
| + skb_pull(skb, offsetof(struct qca_dump_hdr, data0)); |
| |
| usb_disable_autosuspend(udev); |
| bt_dev_info(hdev, "%s memdump size(%u)\n", |
| (pkt_type == HCI_ACLDATA_PKT) ? "ACL" : "event", |
| dump_size); |
| } else { |
| - sk_ptr += offsetof(struct qca_dump_hdr, data); |
| - sk_len -= offsetof(struct qca_dump_hdr, data); |
| + skb_pull(skb, offsetof(struct qca_dump_hdr, data)); |
| } |
| |
| if (!btdata->qca_dump.ram_dump_size) { |
| @@ -3085,7 +3078,6 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| return ret; |
| } |
| |
| - skb_pull(skb, skb->len - sk_len); |
| hci_devcd_append(hdev, skb); |
| btdata->qca_dump.ram_dump_seqno++; |
| if (seqno == QCA_LAST_SEQUENCE_NUM) { |
| @@ -3113,68 +3105,58 @@ static int handle_dump_pkt_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| /* Return: true if the ACL packet is a dump packet, false otherwise. */ |
| static bool acl_pkt_is_dump_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| { |
| - u8 *sk_ptr; |
| - unsigned int sk_len; |
| - |
| struct hci_event_hdr *event_hdr; |
| struct hci_acl_hdr *acl_hdr; |
| struct qca_dump_hdr *dump_hdr; |
| + struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC); |
| + bool is_dump = false; |
| |
| - sk_ptr = skb->data; |
| - sk_len = skb->len; |
| - |
| - acl_hdr = hci_acl_hdr(skb); |
| - if (le16_to_cpu(acl_hdr->handle) != QCA_MEMDUMP_ACL_HANDLE) |
| + if (!clone) |
| return false; |
| |
| - sk_ptr += HCI_ACL_HDR_SIZE; |
| - sk_len -= HCI_ACL_HDR_SIZE; |
| - event_hdr = (struct hci_event_hdr *)sk_ptr; |
| - |
| - if ((event_hdr->evt != HCI_VENDOR_PKT) || |
| - (event_hdr->plen != (sk_len - HCI_EVENT_HDR_SIZE))) |
| - return false; |
| + acl_hdr = skb_pull_data(clone, sizeof(*acl_hdr)); |
| + if (!acl_hdr || (le16_to_cpu(acl_hdr->handle) != QCA_MEMDUMP_ACL_HANDLE)) |
| + goto out; |
| |
| - sk_ptr += HCI_EVENT_HDR_SIZE; |
| - sk_len -= HCI_EVENT_HDR_SIZE; |
| + event_hdr = skb_pull_data(clone, sizeof(*event_hdr)); |
| + if (!event_hdr || (event_hdr->evt != HCI_VENDOR_PKT)) |
| + goto out; |
| |
| - dump_hdr = (struct qca_dump_hdr *)sk_ptr; |
| - if ((sk_len < offsetof(struct qca_dump_hdr, data)) || |
| - (dump_hdr->vse_class != QCA_MEMDUMP_VSE_CLASS) || |
| - (dump_hdr->msg_type != QCA_MEMDUMP_MSG_TYPE)) |
| - return false; |
| + dump_hdr = skb_pull_data(clone, sizeof(*dump_hdr)); |
| + if (!dump_hdr || (dump_hdr->vse_class != QCA_MEMDUMP_VSE_CLASS) || |
| + (dump_hdr->msg_type != QCA_MEMDUMP_MSG_TYPE)) |
| + goto out; |
| |
| - return true; |
| + is_dump = true; |
| +out: |
| + consume_skb(clone); |
| + return is_dump; |
| } |
| |
| /* Return: true if the event packet is a dump packet, false otherwise. */ |
| static bool evt_pkt_is_dump_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| { |
| - u8 *sk_ptr; |
| - unsigned int sk_len; |
| - |
| struct hci_event_hdr *event_hdr; |
| struct qca_dump_hdr *dump_hdr; |
| + struct sk_buff *clone = skb_clone(skb, GFP_ATOMIC); |
| + bool is_dump = false; |
| |
| - sk_ptr = skb->data; |
| - sk_len = skb->len; |
| - |
| - event_hdr = hci_event_hdr(skb); |
| - |
| - if ((event_hdr->evt != HCI_VENDOR_PKT) |
| - || (event_hdr->plen != (sk_len - HCI_EVENT_HDR_SIZE))) |
| + if (!clone) |
| return false; |
| |
| - sk_ptr += HCI_EVENT_HDR_SIZE; |
| - sk_len -= HCI_EVENT_HDR_SIZE; |
| + event_hdr = skb_pull_data(clone, sizeof(*event_hdr)); |
| + if (!event_hdr || (event_hdr->evt != HCI_VENDOR_PKT)) |
| + goto out; |
| |
| - dump_hdr = (struct qca_dump_hdr *)sk_ptr; |
| - if ((sk_len < offsetof(struct qca_dump_hdr, data)) || |
| - (dump_hdr->vse_class != QCA_MEMDUMP_VSE_CLASS) || |
| - (dump_hdr->msg_type != QCA_MEMDUMP_MSG_TYPE)) |
| - return false; |
| + dump_hdr = skb_pull_data(clone, sizeof(*dump_hdr)); |
| + if (!dump_hdr || (dump_hdr->vse_class != QCA_MEMDUMP_VSE_CLASS) || |
| + (dump_hdr->msg_type != QCA_MEMDUMP_MSG_TYPE)) |
| + goto out; |
| |
| - return true; |
| + is_dump = true; |
| +out: |
| + consume_skb(clone); |
| + return is_dump; |
| } |
| |
| static int btusb_recv_acl_qca(struct hci_dev *hdev, struct sk_buff *skb) |
| -- |
| 2.39.5 |
| |