* [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work
@ 2026-09-02 21:46 Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-02 21:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
btusb_close() and btusb_flush() cancel data->rx_work with the
asynchronous cancel_delayed_work(), so if btusb_rx_work() is already
running on another CPU it keeps running after the cancel returns.
btusb_disconnect() calls hci_unregister_dev(), which invokes
btusb_close(), and then frees the btusb_data. A still running
btusb_rx_work() then dereferences the freed data:
while ((skb = skb_dequeue(&data->acl_q)))
data->recv_acl(data->hdev, skb);
Use cancel_delayed_work_sync() instead. In btusb_close() the cancel also
has to happen after btusb_stop_traffic(), otherwise an URB completion
racing with the cancel can requeue the work right after it has been
waited for.
Fixes: 800fe5ec302e ("Bluetooth: btusb: Add support for queuing during polling interval")
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
drivers/bluetooth/btusb.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index 03039ceaa77d..b42963417213 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -2096,18 +2096,24 @@ static int btusb_close(struct hci_dev *hdev)
BT_DBG("%s", hdev->name);
- cancel_delayed_work(&data->rx_work);
cancel_work_sync(&data->work);
cancel_work_sync(&data->waker);
- skb_queue_purge(&data->acl_q);
-
clear_bit(BTUSB_ISOC_RUNNING, &data->flags);
clear_bit(BTUSB_BULK_RUNNING, &data->flags);
clear_bit(BTUSB_INTR_RUNNING, &data->flags);
clear_bit(BTUSB_DIAG_RUNNING, &data->flags);
btusb_stop_traffic(data);
+
+ /* rx_work must only be canceled once the URBs that can rearm it are
+ * gone, and it must be canceled synchronously since btusb_disconnect()
+ * frees the btusb_data it dereferences right after hci_unregister_dev().
+ */
+ cancel_delayed_work_sync(&data->rx_work);
+
+ skb_queue_purge(&data->acl_q);
+
btusb_free_frags(data);
err = usb_autopm_get_interface(data->intf);
@@ -2133,7 +2139,7 @@ static int btusb_flush(struct hci_dev *hdev)
BT_DBG("%s", hdev->name);
- cancel_delayed_work(&data->rx_work);
+ cancel_delayed_work_sync(&data->rx_work);
skb_queue_purge(&data->acl_q);
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core
2026-09-02 21:46 [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work Luiz Augusto von Dentz
@ 2026-09-02 21:46 ` Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 3/3] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-02 21:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
h4_recv_buf() is currently implemented in hci_h4.c which is only built as
part of the hci_uart module, and only when CONFIG_BT_HCIUART_H4 is
enabled. That makes the H:4 reassembly logic unusable by drivers which do
not depend on hci_uart, e.g. btusb which needs it to implement Bulk
Serialization Mode.
Move the transport agnostic part into the Bluetooth core as
h4_recv_skb(), which takes a struct hci_dev instead of a struct hci_uart,
along with struct h4_recv_pkt and the H4_RECV_* helpers, and keep
h4_recv_buf() as a thin wrapper for the hci_uart protocols.
Since every Bluetooth driver already depends on the bluetooth module this
introduces no new module dependency and no new Kconfig symbol.
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
drivers/bluetooth/hci_h4.c | 123 ++-----------------------
drivers/bluetooth/hci_uart.h | 39 +-------
include/net/bluetooth/hci_h4.h | 60 +++++++++++++
net/bluetooth/Makefile | 2 +-
net/bluetooth/hci_h4.c | 160 +++++++++++++++++++++++++++++++++
5 files changed, 229 insertions(+), 155 deletions(-)
create mode 100644 include/net/bluetooth/hci_h4.h
create mode 100644 net/bluetooth/hci_h4.c
diff --git a/drivers/bluetooth/hci_h4.c b/drivers/bluetooth/hci_h4.c
index 767372707498..cbdf51458ec3 100644
--- a/drivers/bluetooth/hci_h4.c
+++ b/drivers/bluetooth/hci_h4.c
@@ -29,6 +29,7 @@
#include <net/bluetooth/bluetooth.h>
#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/hci_h4.h>
#include "hci_uart.h"
@@ -112,8 +113,9 @@ static int h4_recv(struct hci_uart *hu, const void *data, int count)
if (!h4)
return -ENODEV;
- h4->rx_skb = h4_recv_buf(hu, h4->rx_skb, data, count,
- h4_recv_pkts, ARRAY_SIZE(h4_recv_pkts));
+ h4->rx_skb = h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding,
+ h4->rx_skb, data, count, h4_recv_pkts,
+ ARRAY_SIZE(h4_recv_pkts));
if (IS_ERR(h4->rx_skb)) {
int err = PTR_ERR(h4->rx_skb);
bt_dev_err(hu->hdev, "Frame reassembly failed (%d)", err);
@@ -155,120 +157,7 @@ struct sk_buff *h4_recv_buf(struct hci_uart *hu, struct sk_buff *skb,
const unsigned char *buffer, int count,
const struct h4_recv_pkt *pkts, int pkts_count)
{
- u8 alignment = hu->alignment ? hu->alignment : 1;
- struct hci_dev *hdev = hu->hdev;
-
- /* Check for error from previous call */
- if (IS_ERR(skb))
- skb = NULL;
-
- while (count) {
- int i, len;
-
- /* remove padding bytes from buffer */
- for (; hu->padding && count > 0; hu->padding--) {
- count--;
- buffer++;
- }
- if (!count)
- break;
-
- if (!skb) {
- for (i = 0; i < pkts_count; i++) {
- if (buffer[0] != (&pkts[i])->type)
- continue;
-
- skb = bt_skb_alloc((&pkts[i])->maxlen,
- GFP_ATOMIC);
- if (!skb)
- return ERR_PTR(-ENOMEM);
-
- hci_skb_pkt_type(skb) = (&pkts[i])->type;
- hci_skb_expect(skb) = (&pkts[i])->hlen;
- break;
- }
-
- /* Check for invalid packet type */
- if (!skb)
- return ERR_PTR(-EILSEQ);
-
- count -= 1;
- buffer += 1;
- }
-
- len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
- skb_put_data(skb, buffer, len);
-
- count -= len;
- buffer += len;
-
- /* Check for partial packet */
- if (skb->len < hci_skb_expect(skb))
- continue;
-
- for (i = 0; i < pkts_count; i++) {
- if (hci_skb_pkt_type(skb) == (&pkts[i])->type)
- break;
- }
-
- if (i >= pkts_count) {
- kfree_skb(skb);
- return ERR_PTR(-EILSEQ);
- }
-
- if (skb->len == (&pkts[i])->hlen) {
- u16 dlen;
-
- switch ((&pkts[i])->lsize) {
- case 0:
- /* No variable data length */
- dlen = 0;
- break;
- case 1:
- /* Single octet variable length */
- dlen = skb->data[(&pkts[i])->loff];
- hci_skb_expect(skb) += dlen;
-
- if (skb_tailroom(skb) < dlen) {
- kfree_skb(skb);
- return ERR_PTR(-EMSGSIZE);
- }
- break;
- case 2:
- /* Double octet variable length */
- dlen = get_unaligned_le16(skb->data +
- (&pkts[i])->loff);
- hci_skb_expect(skb) += dlen;
-
- if (skb_tailroom(skb) < dlen) {
- kfree_skb(skb);
- return ERR_PTR(-EMSGSIZE);
- }
- break;
- default:
- /* Unsupported variable length */
- kfree_skb(skb);
- return ERR_PTR(-EILSEQ);
- }
-
- if (!dlen) {
- hu->padding = (skb->len + 1) % alignment;
- hu->padding = (alignment - hu->padding) % alignment;
-
- /* No more data, complete frame */
- (&pkts[i])->recv(hdev, skb);
- skb = NULL;
- }
- } else {
- hu->padding = (skb->len + 1) % alignment;
- hu->padding = (alignment - hu->padding) % alignment;
-
- /* Complete frame */
- (&pkts[i])->recv(hdev, skb);
- skb = NULL;
- }
- }
-
- return skb;
+ return h4_recv_skb(hu->hdev, &hu->alignment, &hu->padding, skb, buffer,
+ count, pkts, pkts_count);
}
EXPORT_SYMBOL_GPL(h4_recv_buf);
diff --git a/drivers/bluetooth/hci_uart.h b/drivers/bluetooth/hci_uart.h
index 48ac7ca9334e..7fbe8dffab98 100644
--- a/drivers/bluetooth/hci_uart.h
+++ b/drivers/bluetooth/hci_uart.h
@@ -8,6 +8,8 @@
* Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
*/
+#include <net/bluetooth/hci_h4.h>
+
#ifndef N_HCI
#define N_HCI 15
#endif
@@ -121,43 +123,6 @@ void hci_uart_set_flow_control(struct hci_uart *hu, bool enable);
void hci_uart_set_speeds(struct hci_uart *hu, unsigned int init_speed,
unsigned int oper_speed);
-struct h4_recv_pkt {
- u8 type; /* Packet type */
- u8 hlen; /* Header length */
- u8 loff; /* Data length offset in header */
- u8 lsize; /* Data length field size */
- u16 maxlen; /* Max overall packet length */
- int (*recv)(struct hci_dev *hdev, struct sk_buff *skb);
-};
-
-#define H4_RECV_ACL \
- .type = HCI_ACLDATA_PKT, \
- .hlen = HCI_ACL_HDR_SIZE, \
- .loff = 2, \
- .lsize = 2, \
- .maxlen = HCI_MAX_FRAME_SIZE \
-
-#define H4_RECV_SCO \
- .type = HCI_SCODATA_PKT, \
- .hlen = HCI_SCO_HDR_SIZE, \
- .loff = 2, \
- .lsize = 1, \
- .maxlen = HCI_MAX_SCO_SIZE
-
-#define H4_RECV_EVENT \
- .type = HCI_EVENT_PKT, \
- .hlen = HCI_EVENT_HDR_SIZE, \
- .loff = 1, \
- .lsize = 1, \
- .maxlen = HCI_MAX_EVENT_SIZE
-
-#define H4_RECV_ISO \
- .type = HCI_ISODATA_PKT, \
- .hlen = HCI_ISO_HDR_SIZE, \
- .loff = 2, \
- .lsize = 2, \
- .maxlen = HCI_MAX_FRAME_SIZE \
-
#ifdef CONFIG_BT_HCIUART_H4
int h4_init(void);
int h4_deinit(void);
diff --git a/include/net/bluetooth/hci_h4.h b/include/net/bluetooth/hci_h4.h
new file mode 100644
index 000000000000..a37e7df8c9ce
--- /dev/null
+++ b/include/net/bluetooth/hci_h4.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Bluetooth HCI H:4 packet reassembly
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
+ */
+
+#ifndef __HCI_H4_H
+#define __HCI_H4_H
+
+#include <linux/skbuff.h>
+#include <linux/types.h>
+
+struct hci_dev;
+
+struct h4_recv_pkt {
+ u8 type; /* Packet type */
+ u8 hlen; /* Header length */
+ u8 loff; /* Data length offset in header */
+ u8 lsize; /* Data length field size */
+ u16 maxlen; /* Max overall packet length */
+ int (*recv)(struct hci_dev *hdev, struct sk_buff *skb);
+};
+
+#define H4_RECV_ACL \
+ .type = HCI_ACLDATA_PKT, \
+ .hlen = HCI_ACL_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = HCI_MAX_FRAME_SIZE \
+
+#define H4_RECV_SCO \
+ .type = HCI_SCODATA_PKT, \
+ .hlen = HCI_SCO_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 1, \
+ .maxlen = HCI_MAX_SCO_SIZE
+
+#define H4_RECV_EVENT \
+ .type = HCI_EVENT_PKT, \
+ .hlen = HCI_EVENT_HDR_SIZE, \
+ .loff = 1, \
+ .lsize = 1, \
+ .maxlen = HCI_MAX_EVENT_SIZE
+
+#define H4_RECV_ISO \
+ .type = HCI_ISODATA_PKT, \
+ .hlen = HCI_ISO_HDR_SIZE, \
+ .loff = 2, \
+ .lsize = 2, \
+ .maxlen = HCI_MAX_FRAME_SIZE \
+
+struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding,
+ struct sk_buff *skb, const unsigned char *buffer,
+ int count, const struct h4_recv_pkt *pkts,
+ int pkts_count);
+
+#endif /* __HCI_H4_H */
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index ff466ea97436..b78ad98864d4 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -14,7 +14,7 @@ bluetooth_6lowpan-y := 6lowpan.o
bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
hci_sock.o hci_sysfs.o l2cap_core.o l2cap_sock.o smp.o lib.o \
ecdh_helper.o mgmt_util.o mgmt_config.o hci_codec.o eir.o hci_sync.o \
- hci_drv.o
+ hci_drv.o hci_h4.o
bluetooth-$(CONFIG_DEV_COREDUMP) += coredump.o
diff --git a/net/bluetooth/hci_h4.c b/net/bluetooth/hci_h4.c
new file mode 100644
index 000000000000..86f809018062
--- /dev/null
+++ b/net/bluetooth/hci_h4.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Bluetooth HCI H:4 packet reassembly
+ *
+ * Copyright (C) 2000-2001 Qualcomm Incorporated
+ * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com>
+ * Copyright (C) 2004-2005 Marcel Holtmann <marcel@holtmann.org>
+ */
+
+#include <linux/export.h>
+#include <linux/skbuff.h>
+#include <linux/unaligned.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+#include <net/bluetooth/hci_h4.h>
+
+/* h4_recv_skb - Reassemble H:4 framed packets
+ * @hdev: HCI device the packets are received on
+ * @alignment: optional packet alignment, NULL or 0 means no alignment
+ * @padding: optional padding state carried over between calls
+ * @skb: partially received packet from a previous call, may be NULL or an
+ * ERR_PTR returned by a previous call
+ * @buffer: buffer holding the received data
+ * @count: number of bytes in @buffer
+ * @pkts: table of supported packet types
+ * @pkts_count: number of entries in @pkts
+ *
+ * Returns the partially received packet to be passed to the next call, or an
+ * ERR_PTR on error. The returned value can be fed back into this function as
+ * is, but must be checked with IS_ERR() before being freed.
+ */
+struct sk_buff *h4_recv_skb(struct hci_dev *hdev, u8 *alignment, u8 *padding,
+ struct sk_buff *skb, const unsigned char *buffer,
+ int count, const struct h4_recv_pkt *pkts,
+ int pkts_count)
+{
+ u8 align = alignment && *alignment ? *alignment : 1;
+
+ /* Check for error from previous call */
+ if (IS_ERR(skb))
+ skb = NULL;
+
+ while (count) {
+ int i, len;
+
+ /* remove padding bytes from buffer */
+ if (padding) {
+ for (; (*padding) && count > 0; (*padding)--) {
+ count--;
+ buffer++;
+ }
+ }
+
+ if (!count)
+ break;
+
+ if (!skb) {
+ for (i = 0; i < pkts_count; i++) {
+ if (buffer[0] != pkts[i].type)
+ continue;
+
+ skb = bt_skb_alloc(pkts[i].maxlen,
+ GFP_ATOMIC);
+ if (!skb)
+ return ERR_PTR(-ENOMEM);
+
+ hci_skb_pkt_type(skb) = pkts[i].type;
+ hci_skb_expect(skb) = pkts[i].hlen;
+ break;
+ }
+
+ /* Check for invalid packet type */
+ if (!skb)
+ return ERR_PTR(-EILSEQ);
+
+ count -= 1;
+ buffer += 1;
+ }
+
+ len = min_t(uint, hci_skb_expect(skb) - skb->len, count);
+ skb_put_data(skb, buffer, len);
+
+ count -= len;
+ buffer += len;
+
+ /* Check for partial packet */
+ if (skb->len < hci_skb_expect(skb))
+ continue;
+
+ for (i = 0; i < pkts_count; i++) {
+ if (hci_skb_pkt_type(skb) == pkts[i].type)
+ break;
+ }
+
+ if (i >= pkts_count) {
+ kfree_skb(skb);
+ return ERR_PTR(-EILSEQ);
+ }
+
+ if (skb->len == pkts[i].hlen) {
+ u16 dlen;
+
+ switch (pkts[i].lsize) {
+ case 0:
+ /* No variable data length */
+ dlen = 0;
+ break;
+ case 1:
+ /* Single octet variable length */
+ dlen = skb->data[pkts[i].loff];
+ hci_skb_expect(skb) += dlen;
+
+ if (skb_tailroom(skb) < dlen) {
+ kfree_skb(skb);
+ return ERR_PTR(-EMSGSIZE);
+ }
+ break;
+ case 2:
+ /* Double octet variable length */
+ dlen = get_unaligned_le16(skb->data +
+ pkts[i].loff);
+ hci_skb_expect(skb) += dlen;
+
+ if (skb_tailroom(skb) < dlen) {
+ kfree_skb(skb);
+ return ERR_PTR(-EMSGSIZE);
+ }
+ break;
+ default:
+ /* Unsupported variable length */
+ kfree_skb(skb);
+ return ERR_PTR(-EILSEQ);
+ }
+
+ if (!dlen) {
+ if (padding) {
+ *padding = (skb->len + 1) % align;
+ *padding = (align - *padding) % align;
+ }
+
+ /* No more data, complete frame */
+ pkts[i].recv(hdev, skb);
+ skb = NULL;
+ }
+ } else {
+ if (padding) {
+ *padding = (skb->len + 1) % align;
+ *padding = (align - *padding) % align;
+ }
+
+ /* Complete frame */
+ pkts[i].recv(hdev, skb);
+ skb = NULL;
+ }
+ }
+
+ return skb;
+}
+EXPORT_SYMBOL_GPL(h4_recv_skb);
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 3/3] Bluetooth: btusb: Add support for Bulk Serialization Mode
2026-09-02 21:46 [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
@ 2026-09-02 21:46 ` Luiz Augusto von Dentz
2026-09-03 0:07 ` [v3,1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work bluez.test.bot
2026-09-03 20:00 ` [PATCH v3 1/3] " patchwork-bot+bluetooth
3 siblings, 0 replies; 5+ messages in thread
From: Luiz Augusto von Dentz @ 2026-09-02 21:46 UTC (permalink / raw)
To: linux-bluetooth
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
This adds support for Bulk Serialization Mode introduced in 6.2:
https://www.bluetooth.com/bluetooth-core-6-2-feature-overview/#5-bluetooth-hci-usb-le-isochronous-support
https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-62/out/en/host-controller-interface/usb-transport-layer.html#UUID-c1a65395-29e9-87d3-2981-8bed625d0459
It works by detecting if alternate setting 1 is supported for the
interface and then switches to use it as it serializes all the frames
in a single Bulk endpoint using H4 headers and it considerable more
robust then legacy one while allowing the transport of ISO packets:
'In addition to enabling Bluetooth® LE Audio, the new mode resolves a
persistent race condition in the legacy USB transport layer. In Legacy
Mode, different endpoint types are serviced in a specific order within
a USB frame, which can result in out-of-order delivery of data and
events. For example, a Host might receive a data packet before the
event signaling its arrival. This behavior can disrupt critical
processes such as connection setup, disconnection, and data encryption,
adversely affecting the user experience.'
Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
drivers/bluetooth/btusb.c | 214 ++++++++++++++++++++++++++++++--------
1 file changed, 171 insertions(+), 43 deletions(-)
diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c
index b42963417213..b802c8d7c04d 100644
--- a/drivers/bluetooth/btusb.c
+++ b/drivers/bluetooth/btusb.c
@@ -28,8 +28,9 @@
#include "btbcm.h"
#include "btrtl.h"
#include "btmtk.h"
+#include "hci_uart.h"
-#define VERSION "0.8"
+#define VERSION "1.0"
static bool disable_scofix;
static bool force_scofix;
@@ -985,6 +986,9 @@ struct btqca_data {
#define BTUSB_HW_SSR_ACTIVE 17
#define BTUSB_WAKEUP_BROKEN 18
+#define BTUSB_PROTO_LEGACY 0x00
+#define BTUSB_PROTO_H4 0x01
+
struct btusb_data {
struct hci_dev *hdev;
struct usb_device *udev;
@@ -1018,6 +1022,7 @@ struct btusb_data {
struct sk_buff *evt_skb;
struct sk_buff *acl_skb;
struct sk_buff *sco_skb;
+ struct sk_buff *rx_skb;
struct usb_endpoint_descriptor *intr_ep;
struct usb_endpoint_descriptor *bulk_tx_ep;
@@ -1031,6 +1036,7 @@ struct btusb_data {
__u8 cmdreq_type;
__u8 cmdreq;
+ __u8 proto;
unsigned int sco_num;
unsigned int air_mode;
@@ -1258,6 +1264,11 @@ static inline void btusb_free_frags(struct btusb_data *data)
dev_kfree_skb_irq(data->sco_skb);
data->sco_skb = NULL;
+ /* rx_skb may hold an ERR_PTR from a previous h4_recv_skb() call */
+ if (!IS_ERR(data->rx_skb))
+ dev_kfree_skb_irq(data->rx_skb);
+ data->rx_skb = NULL;
+
spin_unlock_irqrestore(&data->rxlock, flags);
}
@@ -1357,12 +1368,41 @@ static int btusb_recv_acl(struct hci_dev *hdev, struct sk_buff *skb)
return 0;
}
+/* Dispatch through the btusb_recv_* wrappers so that vendor specific
+ * handling (data->recv_event, data->recv_acl) is preserved in H:4 mode.
+ */
+static const struct h4_recv_pkt btusb_recv_pkts[] = {
+ { H4_RECV_ACL, .recv = btusb_recv_acl },
+ { H4_RECV_SCO, .recv = hci_recv_frame },
+ { H4_RECV_EVENT, .recv = btusb_recv_event },
+ { H4_RECV_ISO, .recv = hci_recv_frame },
+};
+
+static int btusb_recv_h4(struct btusb_data *data, void *buffer, int count)
+{
+ unsigned long flags;
+ int err = 0;
+
+ spin_lock_irqsave(&data->rxlock, flags);
+ data->rx_skb = h4_recv_skb(data->hdev, NULL, NULL, data->rx_skb, buffer,
+ count, btusb_recv_pkts,
+ ARRAY_SIZE(btusb_recv_pkts));
+ if (IS_ERR(data->rx_skb))
+ err = PTR_ERR(data->rx_skb);
+ spin_unlock_irqrestore(&data->rxlock, flags);
+
+ return err;
+}
+
static int btusb_recv_bulk(struct btusb_data *data, void *buffer, int count)
{
struct sk_buff *skb;
unsigned long flags;
int err = 0;
+ if (data->proto == BTUSB_PROTO_H4)
+ return btusb_recv_h4(data, buffer, count);
+
spin_lock_irqsave(&data->rxlock, flags);
skb = data->acl_skb;
@@ -2040,12 +2080,14 @@ static int btusb_open(struct hci_dev *hdev)
data->intf->needs_remote_wakeup = 1;
- if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags))
- goto done;
+ if (data->proto == BTUSB_PROTO_LEGACY) {
+ if (test_and_set_bit(BTUSB_INTR_RUNNING, &data->flags))
+ goto done;
- err = btusb_submit_intr_urb(hdev, GFP_KERNEL);
- if (err < 0)
- goto failed;
+ err = btusb_submit_intr_urb(hdev, GFP_KERNEL);
+ if (err < 0)
+ goto failed;
+ }
err = btusb_submit_bulk_urb(hdev, GFP_KERNEL);
if (err < 0) {
@@ -2149,6 +2191,42 @@ static int btusb_flush(struct hci_dev *hdev)
return 0;
}
+static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ struct btusb_data *data = hci_get_drvdata(hdev);
+ struct urb *urb;
+ unsigned int pipe;
+
+ if (!data->bulk_tx_ep)
+ return ERR_PTR(-ENODEV);
+
+ if (data->proto == BTUSB_PROTO_H4) {
+ /* The frame type is prepended in place, so the buffer must not
+ * be shared with anyone else.
+ */
+ if (skb_cow_head(skb, 1))
+ return ERR_PTR(-ENOMEM);
+ }
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return ERR_PTR(-ENOMEM);
+
+ pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress);
+
+ if (data->proto == BTUSB_PROTO_H4) {
+ /* Prepend skb with frame type */
+ memcpy(skb_push(skb, 1), &hci_skb_pkt_type(skb), 1);
+ }
+
+ usb_fill_bulk_urb(urb, data->udev, pipe,
+ skb->data, skb->len, btusb_tx_complete, skb);
+
+ skb->dev = (void *)hdev;
+
+ return urb;
+}
+
static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
{
struct btusb_data *data = hci_get_drvdata(hdev);
@@ -2156,6 +2234,9 @@ static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
struct urb *urb;
unsigned int pipe;
+ if (data->proto == BTUSB_PROTO_H4)
+ return alloc_bulk_urb(hdev, skb);
+
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
return ERR_PTR(-ENOMEM);
@@ -2182,35 +2263,15 @@ static struct urb *alloc_ctrl_urb(struct hci_dev *hdev, struct sk_buff *skb)
return urb;
}
-static struct urb *alloc_bulk_urb(struct hci_dev *hdev, struct sk_buff *skb)
-{
- struct btusb_data *data = hci_get_drvdata(hdev);
- struct urb *urb;
- unsigned int pipe;
-
- if (!data->bulk_tx_ep)
- return ERR_PTR(-ENODEV);
-
- urb = usb_alloc_urb(0, GFP_KERNEL);
- if (!urb)
- return ERR_PTR(-ENOMEM);
-
- pipe = usb_sndbulkpipe(data->udev, data->bulk_tx_ep->bEndpointAddress);
-
- usb_fill_bulk_urb(urb, data->udev, pipe,
- skb->data, skb->len, btusb_tx_complete, skb);
-
- skb->dev = (void *)hdev;
-
- return urb;
-}
-
static struct urb *alloc_isoc_urb(struct hci_dev *hdev, struct sk_buff *skb)
{
struct btusb_data *data = hci_get_drvdata(hdev);
struct urb *urb;
unsigned int pipe;
+ if (data->proto == BTUSB_PROTO_H4)
+ return alloc_bulk_urb(hdev, skb);
+
if (!data->isoc_tx_ep)
return ERR_PTR(-ENODEV);
@@ -2282,6 +2343,22 @@ static int submit_or_queue_tx_urb(struct hci_dev *hdev, struct urb *urb)
return 0;
}
+static int submit_sco_urb(struct hci_dev *hdev, struct urb *urb)
+{
+ struct btusb_data *data = hci_get_drvdata(hdev);
+
+ /* In H:4 mode SCO frames are carried over the bulk endpoint and
+ * complete via btusb_tx_complete(), which decrements tx_in_flight, so
+ * they have to be accounted for like any other bulk transfer.
+ * Isochronous transfers use btusb_isoc_tx_complete() instead, which
+ * does not, so they must not be counted.
+ */
+ if (data->proto == BTUSB_PROTO_H4)
+ return submit_or_queue_tx_urb(hdev, urb);
+
+ return submit_tx_urb(hdev, urb);
+}
+
static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
{
struct urb *urb;
@@ -2315,7 +2392,7 @@ static int btusb_send_frame(struct hci_dev *hdev, struct sk_buff *skb)
return PTR_ERR(urb);
hdev->stat.sco_tx++;
- return submit_tx_urb(hdev, urb);
+ return submit_sco_urb(hdev, urb);
case HCI_ISODATA_PKT:
urb = alloc_bulk_urb(hdev, skb);
@@ -2424,10 +2501,9 @@ static int btusb_switch_alt_setting(struct hci_dev *hdev, int new_alts)
return 0;
}
-static struct usb_host_interface *btusb_find_altsetting(struct btusb_data *data,
- int alt)
+static struct usb_host_interface *
+btusb_find_altsetting(struct usb_interface *intf, int alt)
{
- struct usb_interface *intf = data->isoc;
int i;
BT_DBG("Looking for Alt no :%d", alt);
@@ -2450,6 +2526,13 @@ static void btusb_work(struct work_struct *work)
int new_alts = 0;
int err;
+ /* In H:4 mode SCO/ISO data is carried over the bulk endpoints, so
+ * there is no isochronous interface to resume or to switch alternate
+ * settings on.
+ */
+ if (data->proto == BTUSB_PROTO_H4)
+ return;
+
if (data->sco_num > 0) {
if (!test_bit(BTUSB_DID_ISO_RESUME, &data->flags)) {
err = usb_autopm_get_interface(data->isoc ? data->isoc : data->intf);
@@ -2483,9 +2566,9 @@ static void btusb_work(struct work_struct *work)
* MTU >= 3 (packets) * 25 (size) - 3 (headers) = 72
* see also Core spec 5, vol 4, B 2.1.1 & Table 2.1.
*/
- if (btusb_find_altsetting(data, 6))
+ if (btusb_find_altsetting(data->isoc, 6))
new_alts = 6;
- else if (btusb_find_altsetting(data, 3) &&
+ else if (btusb_find_altsetting(data->isoc, 3) &&
hdev->sco_mtu >= 72 &&
test_bit(BTUSB_USE_ALT3_FOR_WBS, &data->flags))
new_alts = 3;
@@ -2734,8 +2817,13 @@ static int btusb_recv_bulk_intel(struct btusb_data *data, void *buffer,
/* When the device is in bootloader mode, then it can send
* events via the bulk endpoint. These events are treated the
* same way as the ones received from the interrupt endpoint.
+ *
+ * In H:4 mode there is no interrupt endpoint and every frame on the
+ * bulk endpoint carries an H:4 header, including the ones sent by the
+ * bootloader, so the regular decoding applies.
*/
- if (btintel_test_flag(hdev, INTEL_BOOTLOADER))
+ if (data->proto == BTUSB_PROTO_LEGACY &&
+ btintel_test_flag(hdev, INTEL_BOOTLOADER))
return btusb_recv_intr(data, buffer, count);
return btusb_recv_bulk(data, buffer, count);
@@ -2796,7 +2884,7 @@ static int btusb_send_frame_intel(struct hci_dev *hdev, struct sk_buff *skb)
return PTR_ERR(urb);
hdev->stat.sco_tx++;
- return submit_tx_urb(hdev, urb);
+ return submit_sco_urb(hdev, urb);
case HCI_ISODATA_PKT:
urb = alloc_bulk_urb(hdev, skb);
@@ -4007,8 +4095,11 @@ static ssize_t force_poll_sync_write(struct file *file,
if (err)
return err;
- /* Only allow changes while the adapter is down */
- if (test_bit(HCI_UP, &data->hdev->flags))
+ /* Only allow changes while the adapter is down and it is using legacy
+ * protocol.
+ */
+ if (test_bit(HCI_UP, &data->hdev->flags) ||
+ data->proto != BTUSB_PROTO_LEGACY)
return -EPERM;
if (data->poll_sync == enable)
@@ -4107,7 +4198,7 @@ static int btusb_hci_drv_supported_altsettings(struct hci_dev *hdev, void *data,
goto done;
for (i = 0; i <= 6; i++) {
- if (btusb_find_altsetting(drvdata, i))
+ if (btusb_find_altsetting(drvdata->isoc, i))
rp->altsettings[rp->num++] = i;
}
@@ -4161,6 +4252,8 @@ static int btusb_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct gpio_desc *reset_gpio;
+ struct usb_host_interface *alt;
+ struct usb_endpoint_descriptor *bulk_rx_ep, *bulk_tx_ep, *intr_ep;
struct btusb_data *data;
struct hci_dev *hdev;
unsigned ifnum_base;
@@ -4202,10 +4295,38 @@ static int btusb_probe(struct usb_interface *intf,
return -ENOMEM;
data->match_id = id;
+
+ /* Alternate setting 1 with a single pair of bulk endpoints and no
+ * interrupt endpoint means the controller supports Bulk Serialization
+ * Mode, in which every packet is prefixed with an H:4 header and
+ * carried over the bulk endpoints.
+ */
+ alt = btusb_find_altsetting(intf, 1);
+ if (alt && usb_find_int_in_endpoint(alt, &intr_ep) &&
+ !usb_find_common_endpoints(alt, &bulk_rx_ep, &bulk_tx_ep, NULL,
+ NULL)) {
+ err = usb_set_interface(interface_to_usbdev(intf), ifnum_base, 1);
+ if (!err)
+ data->proto = BTUSB_PROTO_H4;
+ else
+ dev_warn(&intf->dev,
+ "failed to select alt setting 1 (%d), using legacy mode",
+ err);
+ }
+
+ /* Check if all endpoints could be enumerated, legacy mode requires
+ * interrupt and bulk endpoints while H4 mode only requires bulk
+ * endpoints.
+ */
err = usb_find_common_endpoints(intf->cur_altsetting, &data->bulk_rx_ep,
- &data->bulk_tx_ep, &data->intr_ep, NULL);
- if (err)
+ &data->bulk_tx_ep,
+ data->proto == BTUSB_PROTO_LEGACY ?
+ &data->intr_ep : NULL,
+ NULL);
+ if (err) {
+ dev_err(&intf->dev, "failed to enumerate endpoints\n");
goto err_free_data;
+ }
if (id->driver_info & BTUSB_AMP) {
data->cmdreq_type = USB_TYPE_CLASS | 0x01;
@@ -4417,6 +4538,12 @@ static int btusb_probe(struct usb_interface *intf,
if (id->driver_info & BTUSB_AMP) {
/* AMP controllers do not support SCO packets */
data->isoc = NULL;
+ } else if (data->proto == BTUSB_PROTO_H4) {
+ /* In H:4 mode every packet, including SCO/ISO, is carried over
+ * the bulk endpoints, so the isochronous interface must not be
+ * claimed nor have its alternate settings switched.
+ */
+ data->isoc = NULL;
} else {
/* Interface orders are hardcoded in the specification */
data->isoc = usb_ifnum_to_if(data->udev, ifnum_base + 1);
@@ -4526,7 +4653,8 @@ static int btusb_probe(struct usb_interface *intf,
if (enable_autosuspend)
usb_enable_autosuspend(data->udev);
- data->poll_sync = enable_poll_sync;
+ if (data->proto == BTUSB_PROTO_LEGACY)
+ data->poll_sync = enable_poll_sync;
err = hci_register_dev(hdev);
if (err < 0)
--
2.54.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* RE: [v3,1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work
2026-09-02 21:46 [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 3/3] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
@ 2026-09-03 0:07 ` bluez.test.bot
2026-09-03 20:00 ` [PATCH v3 1/3] " patchwork-bot+bluetooth
3 siblings, 0 replies; 5+ messages in thread
From: bluez.test.bot @ 2026-09-03 0:07 UTC (permalink / raw)
To: linux-bluetooth, luiz.dentz
[-- Attachment #1: Type: text/plain, Size: 3421 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1156528
---Test result---
Test Summary:
CheckPatch FAIL 3.24 seconds
VerifyFixes PASS 0.14 seconds
VerifySignedoff PASS 0.14 seconds
GitLint FAIL 1.00 seconds
SubjectPrefix PASS 0.40 seconds
BuildKernel PASS 24.49 seconds
CheckAllWarning PASS 26.76 seconds
CheckSparse PASS 22.29 seconds
BuildKernel32 PASS 20.20 seconds
CheckKernelLLVM SKIP 0.00 seconds
TestRunnerSetup PASS 408.63 seconds
TestRunner_l2cap-tester PASS 68.52 seconds
TestRunner_iso-tester PASS 103.83 seconds
TestRunner_bnep-tester PASS 14.86 seconds
TestRunner_mgmt-tester FAIL 223.65 seconds
TestRunner_rfcomm-tester PASS 19.83 seconds
TestRunner_sco-tester PASS 25.26 seconds
TestRunner_ioctl-tester PASS 20.69 seconds
TestRunner_mesh-tester FAIL 20.57 seconds
TestRunner_smp-tester PASS 18.37 seconds
TestRunner_userchan-tester PASS 15.47 seconds
TestRunner_6lowpan-tester PASS 17.96 seconds
IncrementalBuild PASS 26.43 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[v3,2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core
WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
#331:
new file mode 100644
total: 0 errors, 1 warnings, 0 checks, 419 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/github/workspace/src/patch/14785931.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[v3,1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work
13: B3 Line contains hard tab characters (\t): " while ((skb = skb_dequeue(&data->acl_q)))"
14: B3 Line contains hard tab characters (\t): " data->recv_acl(data->hdev, skb);"
##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 501, Passed: 496 (99.0%), Failed: 1, Not Run: 4
Failed Test Cases
Read Exp Feature - Success Failed 0.267 seconds
##############################
Test: TestRunner_mesh-tester - FAIL
Desc: Run mesh-tester with test-runner
Output:
Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0
Failed Test Cases
Mesh - Send cancel - 1 Timed out 1.856 seconds
Mesh - Send cancel - 2 Timed out 1.990 seconds
https://github.com/bluez/bluetooth-next/pull/692
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work
2026-09-02 21:46 [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work Luiz Augusto von Dentz
` (2 preceding siblings ...)
2026-09-03 0:07 ` [v3,1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work bluez.test.bot
@ 2026-09-03 20:00 ` patchwork-bot+bluetooth
3 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+bluetooth @ 2026-09-03 20:00 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hello:
This series was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Wed, 2 Sep 2026 17:46:18 -0400 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
>
> btusb_close() and btusb_flush() cancel data->rx_work with the
> asynchronous cancel_delayed_work(), so if btusb_rx_work() is already
> running on another CPU it keeps running after the cancel returns.
>
> btusb_disconnect() calls hci_unregister_dev(), which invokes
> btusb_close(), and then frees the btusb_data. A still running
> btusb_rx_work() then dereferences the freed data:
>
> [...]
Here is the summary with links:
- [v3,1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work
https://git.kernel.org/bluetooth/bluetooth-next/c/83a1797b2020
- [v3,2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core
https://git.kernel.org/bluetooth/bluetooth-next/c/37521ec2f55f
- [v3,3/3] Bluetooth: btusb: Add support for Bulk Serialization Mode
https://git.kernel.org/bluetooth/bluetooth-next/c/ee16dd607ff4
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-03 20:01 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-02 21:46 [PATCH v3 1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 2/3] Bluetooth: Move H:4 reassembly into the Bluetooth core Luiz Augusto von Dentz
2026-09-02 21:46 ` [PATCH v3 3/3] Bluetooth: btusb: Add support for Bulk Serialization Mode Luiz Augusto von Dentz
2026-09-03 0:07 ` [v3,1/3] Bluetooth: btusb: Fix UAF of btusb_data by rx_work bluez.test.bot
2026-09-03 20:00 ` [PATCH v3 1/3] " patchwork-bot+bluetooth
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox