Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH v3] Bluetooth: MGMT: Fix mesh_tx Use-After-Free and leak in mesh_send()
       [not found] <20260919080846.3005471-1-benquike@gmail.com>
@ 2026-09-19 11:25 ` Hui Peng
  2026-09-19 11:49   ` [v3] " bluez.test.bot
  2026-09-19 11:55   ` [PATCH v3] " Hui Peng
  0 siblings, 2 replies; 3+ messages in thread
From: Hui Peng @ 2026-09-19 11:25 UTC (permalink / raw)
  To: marcel, luiz.dentz; +Cc: linux-bluetooth, linux-kernel

In mesh_send(), a struct mgmt_mesh_tx entry is allocated and added to
hdev->mesh_pending via mgmt_mesh_add(sk, hdev, send, len) before queuing
mesh_send_sync() and mesh_send_start_complete() onto hdev->req_workqueue
with mesh_tx as the callback data pointer.

Before or while the workqueue executes mesh_send_sync() and
mesh_send_start_complete(), mesh_tx can be removed from
hdev->mesh_pending and freed via mgmt_mesh_remove():
1. If an earlier MGMT_OP_MESH_SEND_CANCEL (send_cancel()) item runs on
   hdev->req_workqueue, send_cancel() calls mesh_send_complete(), which
   removes and frees mesh_tx.
2. If the management socket is closed, mgmt_cleanup() removes and frees
   all mesh_tx entries matching sk under hci_dev_lock(hdev).

When mesh_send_sync() and mesh_send_start_complete() subsequently run,
they dereference the dangling mesh_tx pointer (mesh_tx->param,
mesh_tx->handle, mesh_tx->instance), and mesh_send_start_complete()
frees mesh_tx a second time via mesh_send_complete() on error.

Furthermore, if hci_cmd_sync_queue() fails in mesh_send() (which can
only happen in the !sending branch), mesh_send() only called
mgmt_mesh_remove(mesh_tx) when if (sending) was true (an inverted
condition), leaking mesh_tx on hdev->mesh_pending.

Fix this by holding hci_dev_lock(hdev) and verifying
mesh_tx == mgmt_mesh_next(hdev, NULL) across all mesh_tx and send
dereferences in mesh_send_sync() and mesh_send_start_complete(), and
unconditionally calling mgmt_mesh_remove(mesh_tx) on
hci_cmd_sync_queue() error in mesh_send().

Kernel stack trace (Linux 7.3.0-rc3):
 ==================================================================
 BUG: KASAN: slab-use-after-free in mesh_send_sync+0x39a/0x410
 Read of size 1 at addr ffff8880132fe53b by task kworker/u9:1/143

 CPU: 1 UID: 0 PID: 143 Comm: kworker/u9:1 Not tainted 7.3.0-rc3-g5dd1818b15d9 #1 PREEMPT(lazy)
 Workqueue: hci0 hci_cmd_sync_work
 Call Trace:
  <TASK>
  dump_stack_lvl+0x70/0xa0
  print_report+0x153/0x4c6
  kasan_report+0xf1/0x120
  mesh_send_sync+0x39a/0x410
  hci_cmd_sync_work+0x14e/0x2a0
  process_one_work+0x6ff/0x1110
  worker_thread+0x4a8/0xb70
  kthread+0x307/0x3e0
  ret_from_fork+0x3ed/0x680
  ret_from_fork_asm+0x1a/0x30
  </TASK>

 Allocated by task 1:
  kasan_save_stack+0x30/0x50
  kasan_save_track+0x14/0x30
  __kasan_kmalloc+0x7f/0x90
  __kmalloc_cache_noprof+0x16a/0x380
  mgmt_mesh_add+0x5b/0x340
  mesh_send+0x22b/0x690
  hci_sock_sendmsg+0x1155/0x2040
  sock_write_iter+0x492/0x520
  vfs_write+0x671/0xd20
  ksys_write+0x1bb/0x210
  do_syscall_64+0xda/0x4b0
  entry_SYSCALL_64_after_hwframe+0x77/0x7f

 Freed by task 143:
  kasan_save_stack+0x30/0x50
  kasan_save_track+0x14/0x30
  kasan_save_free_info+0x3b/0x70
  __kasan_slab_free+0x47/0x70
  kfree+0x159/0x420
  send_cancel+0x14e/0x360
  hci_cmd_sync_work+0x14e/0x2a0
  process_one_work+0x6ff/0x1110
  worker_thread+0x4a8/0xb70
  kthread+0x307/0x3e0
  ret_from_fork+0x3ed/0x680
  ret_from_fork_asm+0x1a/0x30

 The buggy address belongs to the object at 0xffff8880132fe500
  which belongs to the cache kmalloc-96 of size 96
 The buggy address is located 59 bytes inside of
  freed 96-byte region [ffff8880132fe500, ffff8880132fe560)
 ==================================================================

Fixes: b338d91703fa ("Bluetooth: Implement support for Mesh")
Assisted-by: LLM
Signed-off-by: Hui Peng <benquike@gmail.com>
---
v3: Add a Fixes: tag. The allocator (mgmt_mesh_add() in mesh_send()),
the freeing path (mesh_send_complete() from send_cancel()), the racing
dereference (mesh_send_sync()), the unlocked hci_add_adv_instance()
call and the missing mgmt_mesh_remove() on the !sending path all came
in together with b338d91703fa ("Bluetooth: Implement support for
Mesh"), first released in v6.1. No later commit touched any of the
lines this patch changes.

This patch fixes two things that share that one origin: the
UAF/locking race, and the leak when the send is not queued. Happy to
split it into two patches if you prefer - both halves would carry the
same tag.

Backporting: the hunks apply back to v6.1, but mesh_send() picked up
extra validation in bda93eec78cd, so older stable branches may need a
trivial context fixup.

v2: Resend via git send-email with intact tab formatting and Assisted-by: LLM tag.
Reproducer (triggers KASAN slab-use-after-free in mesh_send_sync() on
Linux 7.3.0-rc3 via /dev/vhci and MGMT_OP_MESH_SEND_CANCEL +
MGMT_OP_MESH_SEND):

  /* 1. Enable Mesh experimental feature (UUID 766ef3e8-245f-05bf-8d4d-037ad763e42c)
   *    and LE on hci0 via MGMT_OP_SET_EXP_FEATURE (0x004a) and MGMT_OP_SET_LE (0x000d).
   * 2. Hold hdev->req_workqueue briefly in set_name_sync (MGMT_OP_SET_LOCAL_NAME 0x000f).
   * 3. Queue MGMT_OP_MESH_SEND_CANCEL (0x005a, handle = 0) followed immediately by
   *    MGMT_OP_MESH_SEND (0x0059) on a second MGMT control socket.
   * 4. MGMT_OP_MESH_SEND synchronously adds mesh_tx to hdev->mesh_pending (assigning
   *    handle = 1, or matching handle = 0 cancel) and queues mesh_send_sync(mesh_tx).
   *    When send_cancel() runs first on hdev->req_workqueue, it frees mesh_tx, and
   *    mesh_send_sync() immediately dereferences the freed mesh_tx. */

 net/bluetooth/mgmt.c | 25 ++++++++++++++++++-------
 1 file changed, 18 insertions(+), 7 deletions(-)

diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index ac4864e56..fbf85e791 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -2306,36 +2306,53 @@ static int set_mesh(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 static void mesh_send_start_complete(struct hci_dev *hdev, void *data, int err)
 {
 	struct mgmt_mesh_tx *mesh_tx = data;
-	struct mgmt_cp_mesh_send *send = (void *)mesh_tx->param;
+	struct mgmt_cp_mesh_send *send;
 	unsigned long mesh_send_interval;
 	u8 mgmt_err = mgmt_status(err);
 
-	/* Report any errors here, but don't report completion */
+	hci_dev_lock(hdev);
+	if (mesh_tx != mgmt_mesh_next(hdev, NULL)) {
+		hci_dev_unlock(hdev);
+		return;
+	}
 
+	/* Report any errors here, but don't report completion */
 	if (mgmt_err) {
 		hci_dev_clear_flag(hdev, HCI_MESH_SENDING);
 		/* Send Complete Error Code for handle */
 		mesh_send_complete(hdev, mesh_tx, false);
+		hci_dev_unlock(hdev);
 		return;
 	}
 
+	send = (void *)mesh_tx->param;
 	mesh_send_interval = msecs_to_jiffies((send->cnt) * 25);
 	queue_delayed_work(hdev->req_workqueue, &hdev->mesh_send_done,
 			   mesh_send_interval);
+	hci_dev_unlock(hdev);
 }
 
 static int mesh_send_sync(struct hci_dev *hdev, void *data)
 {
 	struct mgmt_mesh_tx *mesh_tx = data;
-	struct mgmt_cp_mesh_send *send = (void *)mesh_tx->param;
+	struct mgmt_cp_mesh_send *send;
 	struct adv_info *adv, *next_instance;
 	u8 instance = hdev->le_num_of_adv_sets + 1;
 	u16 timeout, duration;
 	int err = 0;
 
-	if (hdev->le_num_of_adv_sets <= hdev->adv_instance_cnt)
+	hci_dev_lock(hdev);
+	if (mesh_tx != mgmt_mesh_next(hdev, NULL)) {
+		hci_dev_unlock(hdev);
+		return MGMT_STATUS_FAILED;
+	}
+
+	if (hdev->le_num_of_adv_sets <= hdev->adv_instance_cnt) {
+		hci_dev_unlock(hdev);
 		return MGMT_STATUS_BUSY;
+	}
 
+	send = (void *)mesh_tx->param;
 	timeout = 1000;
 	duration = send->cnt * INTERVAL_TO_MS(hdev->le_adv_max_interval);
 	adv = hci_add_adv_instance(hdev, instance, 0,
@@ -2372,6 +2389,8 @@ static int mesh_send_sync(struct hci_dev *hdev, void *data)
 		instance = 0;
 	}
 
+	hci_dev_unlock(hdev);
+
 	if (instance)
 		return hci_schedule_adv_instance_sync(hdev, instance, true);
 
@@ -2534,10 +2553,8 @@ static int mesh_send(struct sock *sk, struct hci_dev *hdev, void *data, u16 len)
 		err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_MESH_SEND,
 				      MGMT_STATUS_FAILED);
 
-		if (mesh_tx) {
-			if (sending)
-				mgmt_mesh_remove(mesh_tx);
-		}
+		if (mesh_tx)
+			mgmt_mesh_remove(mesh_tx);
 	} else {
 		hci_dev_set_flag(hdev, HCI_MESH_SENDING);
 
-- 
2.43.0

^ permalink raw reply related	[flat|nested] 3+ messages in thread

* RE: [v3] Bluetooth: MGMT: Fix mesh_tx Use-After-Free and leak in mesh_send()
  2026-09-19 11:25 ` [PATCH v3] Bluetooth: MGMT: Fix mesh_tx Use-After-Free and leak in mesh_send() Hui Peng
@ 2026-09-19 11:49   ` bluez.test.bot
  2026-09-19 11:55   ` [PATCH v3] " Hui Peng
  1 sibling, 0 replies; 3+ messages in thread
From: bluez.test.bot @ 2026-09-19 11:49 UTC (permalink / raw)
  To: linux-bluetooth, benquike

[-- Attachment #1: Type: text/plain, Size: 839 bytes --]

This is an automated email and please do not reply to this email.

Dear Submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.

----- Output -----

error: patch failed: net/bluetooth/mgmt.c:2306
error: net/bluetooth/mgmt.c: patch does not apply
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"

Please resolve the issue and submit the patches again.


---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v3] Bluetooth: MGMT: Fix mesh_tx Use-After-Free and leak in mesh_send()
  2026-09-19 11:25 ` [PATCH v3] Bluetooth: MGMT: Fix mesh_tx Use-After-Free and leak in mesh_send() Hui Peng
  2026-09-19 11:49   ` [v3] " bluez.test.bot
@ 2026-09-19 11:55   ` Hui Peng
  1 sibling, 0 replies; 3+ messages in thread
From: Hui Peng @ 2026-09-19 11:55 UTC (permalink / raw)
  To: marcel, luiz.dentz; +Cc: lee, linux-bluetooth, linux-kernel

Please drop this patch, and sorry for the noise.

Two things are wrong with it.

First, the mechanical problem the CI already reported: I based it on
Linus' tree rather than bluetooth-next, so it does not apply.

Second, and more to the point, the use-after-free half of it is no
longer needed. 71af682ba469 ("Bluetooth: mgmt: Dequeue pending
mesh_send_sync entries on cancel") fixed that on 2026-09-15, and fixed
it better than this patch did. It dequeues the pending mesh_send_sync
entry at the point where mesh_tx is freed, which addresses the problem
at its source. What I had done instead was take hci_dev_lock() across
mesh_send_sync() and re-validate mesh_tx against mgmt_mesh_next(), which
only narrows the window for the two callers I happened to look at and
would not help any future path that frees a queued mesh_tx. I should
have checked bluetooth-next before sending.

What does survive is the other half: mesh_tx is leaked when
hci_cmd_sync_queue() fails in mesh_send(), because the cleanup is
guarded by "if (sending)" while that error can only occur when sending
is false. That is still present in bluetooth-next today. I have sent it
separately as a one-hunk patch against the right tree:

  [PATCH] Bluetooth: MGMT: fix mesh_tx leak on hci_cmd_sync_queue() failure
  Message-ID: <20260919115436.3998954-1-benquike@gmail.com>

Hui


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-09-19 11:55 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260919080846.3005471-1-benquike@gmail.com>
2026-09-19 11:25 ` [PATCH v3] Bluetooth: MGMT: Fix mesh_tx Use-After-Free and leak in mesh_send() Hui Peng
2026-09-19 11:49   ` [v3] " bluez.test.bot
2026-09-19 11:55   ` [PATCH v3] " Hui Peng

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