[PATCH v3 3/3] Bluetooth: SMP: Serialize root channel replacement
flat view
WARM1d
From: Cen Zhang <hidden>
Date: 2026-10-31 07:32:45
Also in:
lkml
Subsystem:
bluetooth subsystem, the rest · Maintainers:
Marcel Holtmann, Luiz Augusto von Dentz, Linus Torvalds
smp_register() replaces the SMP root channels when they are still registered from an earlier power-on. This happens when management powers on a controller that is already up with HCI_AUTO_OFF set: hci_power_on_sync() clears the flag and runs hci_powered_update_sync() again, which registers SMP so that the channels use the identity address chosen for this power-on. The WARN_ON() in smp_register() fires on this path, and the old channels and their SMP data are freed while two kinds of users can still hold them. HCI event handlers read hdev->smp_data under hdev->lock, for example smp_irk_matches() when resolving the target of a directed advertising report: HCI receive worker Power-on request hci_dev_lock(hdev) smp_irk_matches() chan = hdev->smp_data hci_powered_update_sync() smp_register() smp_del_chan(chan), chan freed reads chan->data SMP receive callbacks run on hdev->workqueue without hdev->lock and use the ECDH transform of the root, for example smp_cmd_public_key(): HCI receive worker (SMP) Power-on request smp_cmd_public_key() tfm = root's tfm_ecdh smp_register() smp_del_chan() crypto_free_kpp(tfm) compute_ecdh_secret(tfm, ...) Before commit 2ff13894cfb8 ("Bluetooth: Perform HCI update for power on synchronously") moved it to the request worker, registration ran from the completion callback of the power-on request in the HCI receive worker, which serialized it with both users. Run the registration on hdev->workqueue again and hold hdev->lock while it replaces the channels. smp_register() waits for the work and returns its result. Its callers hold the request lock but not hdev->lock, including hci_power_on() when it updates a controller in the HCI_AUTO_OFF window. No work on hdev->workqueue takes the request lock, and hci_dev_close_sync() drains this workqueue only with the request lock held, so the registration work cannot be queued while it drains. Users that run under the request lock, such as smp_generate_rpa(), stay serialized by that lock. The reports below come from test kernels with scheduling hooks that widen these windows. The WARN_ON() in smp_register() was printed before each of them. KASAN report as below: ================================================================== BUG: KASAN: slab-use-after-free in smp_irk_matches+0x2fb/0x310 Read of size 8 at addr ffff88810c35f4a0 by task kworker/u17:0/520 Workqueue: hci0 hci_rx_work Call Trace: <TASK> dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 kasan_report+0xe0/0x110 smp_irk_matches+0x2fb/0x310 process_adv_report.part.0+0x2ce/0x1680 hci_le_direct_adv_report_evt+0x2f8/0x4c0 hci_le_meta_evt+0x30c/0x5c0 hci_event_packet+0x4e8/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> Allocated by task 522: 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+0x120/0x4f0 smp_register+0xa7/0x400 hci_powered_update_sync+0x88/0x720 hci_dev_open_sync+0xae0/0x2060 hci_set_powered_sync+0x2a9/0x720 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 Freed by task 522: 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_del_chan+0x9f/0x170 smp_register+0x2e8/0x400 hci_powered_update_sync+0x88/0x720 hci_set_powered_sync+0x5e5/0x720 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 ffff88810c35f000 which belongs to the cache kmalloc-2k of size 2048 The buggy address is located 1184 bytes inside of freed 2048-byte region [ffff88810c35f000, ffff88810c35f800) ================================================================== KASAN report as below: ================================================================== BUG: KASAN: slab-use-after-free in compute_ecdh_secret+0x856/0xa50 Read of size 4 at addr ffff8881071f4f00 by task kworker/u17:2/500 Workqueue: hci1 hci_rx_work Call Trace: <TASK> dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 kasan_report+0xe0/0x110 compute_ecdh_secret+0x856/0xa50 smp_recv_cb+0x3acf/0x93b0 l2cap_recv_frame+0x13cc/0x8830 l2cap_recv_acldata+0xe9f/0x10e0 hci_rx_work+0x5a6/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> Allocated by task 497: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_node_noprof+0x2f2/0x7b0 crypto_create_tfm_node+0x92/0x390 crypto_alloc_tfm_node+0xf5/0x220 smp_add_cid+0xc1/0x4f0 smp_register+0x9d/0x430 hci_powered_update_sync+0x82/0x680 hci_set_powered_sync+0x4f4/0x750 set_powered_sync+0xb8/0x210 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 Freed by task 497: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 crypto_destroy_tfm+0xdd/0x230 smp_del_chan+0x8b/0x180 smp_register+0x29d/0x430 hci_powered_update_sync+0x82/0x680 hci_set_powered_sync+0x4f4/0x750 set_powered_sync+0xb8/0x210 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 ffff8881071f4f00 which belongs to the cache kmalloc-128 of size 128 The buggy address is located 0 bytes inside of freed 128-byte region [ffff8881071f4f00, ffff8881071f4f80) ================================================================== Fixes: 2ff13894cfb8 ("Bluetooth: Perform HCI update for power on synchronously") Signed-off-by: Cen Zhang <redacted> --- Assisted-by: LLM net/bluetooth/smp.c | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 0e442cc2a35da..552b6b3713c3a 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c@@ -3369,7 +3369,8 @@ int smp_force_bredr(struct hci_dev *hdev, bool enable) return 0; } -int smp_register(struct hci_dev *hdev) +static int __smp_register(struct hci_dev *hdev) + __must_hold(&hdev->lock) { struct l2cap_chan *chan;
@@ -3419,6 +3420,39 @@ int smp_register(struct hci_dev *hdev) return 0; } +struct smp_register_work { + struct work_struct work; + struct hci_dev *hdev; + int err; +}; + +static void smp_register_func(struct work_struct *work) +{ + struct smp_register_work *reg = + container_of(work, struct smp_register_work, work); + struct hci_dev *hdev = reg->hdev; + + hci_dev_lock(hdev); + reg->err = __smp_register(hdev); + hci_dev_unlock(hdev); +} + +int smp_register(struct hci_dev *hdev) +{ + struct smp_register_work reg = { .hdev = hdev }; + + /* HCI event handlers use the root channels under hdev->lock, and + * SMP receive callbacks use them from hdev->workqueue. Replace the + * channels from that workqueue while holding hdev->lock. + */ + INIT_WORK_ONSTACK(®.work, smp_register_func); + queue_work(hdev->workqueue, ®.work); + flush_work(®.work); + destroy_work_on_stack(®.work); + + return reg.err; +} + void smp_unregister(struct hci_dev *hdev) { struct l2cap_chan *chan;