From: Cen Zhang <zzzccc427@gmail.com>
To: marcel@holtmann.org, luiz.dentz@gmail.com
Cc: linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org,
baijiaju1990@gmail.com, jjzuming@gmail.com, zzzccc427@gmail.com
Subject: [PATCH v2 1/2] Bluetooth: SMP: Serialize forced BR/EDR channel changes
Date: Sat, 31 Oct 2026 11:27:13 +0800 [thread overview]
Message-ID: <pm-bluetooth-objects-candidate-0184-v8-60106ad0c49170f5bfb4-1@gmail.com> (raw)
In-Reply-To: <pm-bluetooth-objects-candidate-0184-v8-60106ad0c49170f5bfb4-0@gmail.com>
The force_bredr_smp debugfs switch creates and removes the BR/EDR SMP
root channel in hdev->smp_bredr_data without holding hdev->lock. The
other users of that channel hold the lock: l2cap_connect_cfm() looks up
fixed channels during HCI event processing, and hci_dev_close_sync()
removes the root through smp_unregister().
Enabling the switch can let a connection confirmation use a root that
is not fully initialized. smp_add_cid() publishes the channel through
l2cap_chan_create() and sets BT_LISTEN before it assigns the channel
operations and the parent lock nesting level:
Debugfs writer HCI receive worker
smp_add_cid()
l2cap_chan_create()
l2cap_add_scid()
chan->state = BT_LISTEN
hci_dev_lock(hdev)
l2cap_connect_cfm()
l2cap_global_fixed_chan()
l2cap_chan_lock(root)
l2cap_new_connection()
root->ops->new_connection()
chan->ops = &smp_root_chan_ops
Disabling the switch can race with power-off, which releases the same
channel:
Debugfs writer Power-off
smp_force_bredr(hdev, false)
chan = hdev->smp_bredr_data
hci_dev_lock(hdev)
smp_unregister(hdev)
smp_del_chan(chan), chan is freed
hci_dev_unlock(hdev)
smp_del_chan(chan)
reads chan->data after the free
Hold hdev->lock around smp_force_bredr() in the debugfs writer and
annotate that requirement. smp_unregister() leaves HCI_FORCE_BREDR_SMP
set, so after power-off the slot is empty while the switch still reads
as enabled. Disabling it then passes NULL to smp_del_chan() even without
a race; skip the deletion in that case and only clear the flag.
Both reports below come from test kernels with scheduling hooks that
widen these windows. In the enable case lockdep also reported recursive
locking just before the oops, because the root still had the default
nesting level.
Oops report as below:
Oops: general protection fault, probably for non-canonical address
0xdffffc0000000001: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f]
Workqueue: hci1 hci_rx_work
RIP: 0010:l2cap_new_connection+0x16c/0x320
Call Trace:
<TASK>
l2cap_connect_cfm+0x46c/0xe60
hci_remote_ext_features_evt+0x67d/0xbd0
hci_event_packet+0x894/0xc70
hci_rx_work+0x367/0xf00
process_one_work+0x908/0x19c0
worker_thread+0x65c/0xe40
kthread+0x34f/0x460
ret_from_fork+0x659/0x940
ret_from_fork_asm+0x1a/0x30
</TASK>
KASAN report as below:
==================================================================
BUG: KASAN: slab-use-after-free in smp_del_chan+0xd2/0xe0
Read of size 8 at addr ffff888112a2e4a0 by task sh/493
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
kasan_report+0xe0/0x110
smp_del_chan+0xd2/0xe0
smp_force_bredr+0xda/0x200
force_bredr_smp_write+0xc9/0x130
full_proxy_write+0x11f/0x180
vfs_write+0x25a/0x1010
ksys_write+0x111/0x200
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
</TASK>
Allocated by task 493:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x251/0x610
l2cap_chan_create+0x47/0x930
smp_add_cid+0x309/0x4f0
smp_force_bredr+0x17d/0x200
force_bredr_smp_write+0xc9/0x130
full_proxy_write+0x11f/0x180
vfs_write+0x25a/0x1010
ksys_write+0x111/0x200
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 496:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x5f/0x80
kfree+0x307/0x580
l2cap_chan_put+0x273/0x3a0
smp_unregister+0x59/0xe0
hci_dev_close_sync+0x4c9/0x1150
hci_set_powered_sync+0x4a7/0x680
set_powered_sync+0xaa/0x150
hci_cmd_sync_work+0x1b3/0x450
process_one_work+0x908/0x19c0
worker_thread+0x65c/0xe40
kthread+0x34f/0x460
ret_from_fork+0x659/0x940
ret_from_fork_asm+0x1a/0x30
The buggy address belongs to the object at ffff888112a2e000
which belongs to the cache kmalloc-2k of size 2048
The buggy address is located 1184 bytes inside of
freed 2048-byte region [ffff888112a2e000, ffff888112a2e800)
==================================================================
Fixes: 300acfdec916 ("Bluetooth: Introduce force_bredr_smp debugfs option for testing")
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
Assisted-by: LLM
net/bluetooth/hci_debugfs.c | 2 ++
net/bluetooth/smp.c | 2 +-
net/bluetooth/smp.h | 3 ++-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c
index cd5ef398f2382..f513731053d28 100644
--- a/net/bluetooth/hci_debugfs.c
+++ b/net/bluetooth/hci_debugfs.c
@@ -525,7 +525,9 @@ static ssize_t force_bredr_smp_write(struct file *file,
if (err)
return err;
+ hci_dev_lock(hdev);
err = smp_force_bredr(hdev, enable);
+ hci_dev_unlock(hdev);
if (err)
return err;
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 984b2d0b5e3e9..0e442cc2a35da 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -3356,7 +3356,7 @@ int smp_force_bredr(struct hci_dev *hdev, bool enable)
return PTR_ERR(chan);
hdev->smp_bredr_data = chan;
- } else {
+ } else if (hdev->smp_bredr_data) {
struct l2cap_chan *chan;
chan = hdev->smp_bredr_data;
diff --git a/net/bluetooth/smp.h b/net/bluetooth/smp.h
index c86c463890078..13fe40c0cb244 100644
--- a/net/bluetooth/smp.h
+++ b/net/bluetooth/smp.h
@@ -193,7 +193,8 @@ bool smp_irk_matches(struct hci_dev *hdev, const u8 irk[16],
int smp_generate_rpa(struct hci_dev *hdev, const u8 irk[16], bdaddr_t *rpa);
int smp_generate_oob(struct hci_dev *hdev, u8 hash[16], u8 rand[16]);
-int smp_force_bredr(struct hci_dev *hdev, bool enable);
+int smp_force_bredr(struct hci_dev *hdev, bool enable)
+ __must_hold(&hdev->lock);
int smp_register(struct hci_dev *hdev);
void smp_unregister(struct hci_dev *hdev);
next prev parent reply other threads:[~2026-10-31 3:27 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-31 3:27 [PATCH v2 0/2] Bluetooth: SMP: Serialize root channel updates Cen Zhang
2026-10-31 3:27 ` Cen Zhang [this message]
2026-10-31 3:27 ` [PATCH v2 2/2] Bluetooth: SMP: Serialize root channel replacement Cen Zhang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pm-bluetooth-objects-candidate-0184-v8-60106ad0c49170f5bfb4-1@gmail.com \
--to=zzzccc427@gmail.com \
--cc=baijiaju1990@gmail.com \
--cc=jjzuming@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox