[PATCH v2 3/6] Bluetooth: hci_event: Disconnect links that complete while being aborted

From: Hitalo Souza

Date: Thu Oct 08 2026 - 11:14:20 EST


Since commit 16e3b6429159 ("Bluetooth: hci_conn: Fix modifying handle
while aborting"), and for LE since commit 181a42edddf5 ("Bluetooth: Make
handle of hci_conn be unique"), a connection that is being aborted
refuses the handle of its link when the link completes before the
connection is deleted, e.g. while the abort waits for its cancel
command. The link is up in the controller, though, and nothing
disconnects it: the event handler fails the connection or leaves it to
the abort, and the abort cancels the connect attempt and finds no handle
to disconnect. The handle then stays in use in the controller, e.g. a
SCO link left this way makes the controller reject every later setup
for the device until the ACL drops.

Disconnect the link when its connection refuses the handle, on
Connection Complete, Synchronous Connection Complete and LE (Enhanced)
Connection Complete, with the reason the connection was aborted for.
This is not done in hci_conn_set_handle() itself, as its other callers
set the handles of links that are not up yet.

Fixes: 16e3b6429159 ("Bluetooth: hci_conn: Fix modifying handle while aborting")
Fixes: 181a42edddf5 ("Bluetooth: Make handle of hci_conn be unique")
Assisted-by: LLM
Signed-off-by: Hitalo Souza <enghitalo@xxxxxxxxx>
---
net/bluetooth/hci_event.c | 41 ++++++++++++++++++++++++++++++++++++---
1 file changed, 38 insertions(+), 3 deletions(-)

diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index cbe53e19e..1f9b47389 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -3217,6 +3217,39 @@ static int hci_read_enc_key_size(struct hci_dev *hdev, struct hci_conn *conn)
return hci_send_cmd(hdev, HCI_OP_READ_ENC_KEY_SIZE, sizeof(cp), &cp);
}

+/* Disconnects a link that came up in the controller but that no connection
+ * takes: nothing else would, and its handle would stay in use there.
+ */
+static void hci_disconnect_unused(struct hci_dev *hdev, u16 handle, u8 reason)
+{
+ struct hci_cp_disconnect cp;
+
+ /* Never for an invalid handle or one that a connection uses */
+ if (handle > HCI_CONN_HANDLE_MAX ||
+ hci_conn_hash_lookup_handle(hdev, handle))
+ return;
+
+ bt_dev_dbg(hdev, "handle 0x%4.4x reason 0x%2.2x", handle, reason);
+
+ cp.handle = cpu_to_le16(handle);
+ cp.reason = reason;
+ hci_send_cmd(hdev, HCI_OP_DISCONNECT, sizeof(cp), &cp);
+}
+
+/* Sets the handle of a connection whose link came up. A connection that is
+ * being aborted refuses it, so its link is disconnected: the abort only
+ * cancels the connect attempt, and finds no handle to disconnect.
+ */
+static u8 hci_conn_complete_set_handle(struct hci_conn *conn, u16 handle)
+{
+ u8 status = hci_conn_set_handle(conn, handle);
+
+ if (status && conn->abort_reason)
+ hci_disconnect_unused(conn->hdev, handle, conn->abort_reason);
+
+ return status;
+}
+
static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,
struct sk_buff *skb)
{
@@ -3292,7 +3325,8 @@ static void hci_conn_complete_evt(struct hci_dev *hdev, void *data,
}

if (!status) {
- status = hci_conn_set_handle(conn, __le16_to_cpu(ev->handle));
+ status = hci_conn_complete_set_handle(conn,
+ __le16_to_cpu(ev->handle));
if (status)
goto done;

@@ -5208,7 +5242,8 @@ static void hci_sync_conn_complete_evt(struct hci_dev *hdev, void *data,

switch (status) {
case 0x00:
- status = hci_conn_set_handle(conn, __le16_to_cpu(ev->handle));
+ status = hci_conn_complete_set_handle(conn,
+ __le16_to_cpu(ev->handle));
if (status) {
conn->state = BT_CLOSED;
break;
@@ -5974,7 +6009,7 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
* hci_conn_failed function which is triggered by the HCI
* request completion callbacks used for connecting.
*/
- if (status || hci_conn_set_handle(conn, handle))
+ if (status || hci_conn_complete_set_handle(conn, handle))
goto unlock;

/* Drop the connection if it has been aborted */
--
2.55.0