* [PATCH] Bluetooth: MGMT: Reject LTK loads while connections exist
@ 2026-10-13 16:26 Cen Zhang
2026-10-13 20:08 ` bluez.test.bot
0 siblings, 1 reply; 2+ messages in thread
From: Cen Zhang @ 2026-10-13 16:26 UTC (permalink / raw)
To: marcel, luiz.dentz, johan.hedberg, vinicius.gomes
Cc: linux-bluetooth, linux-kernel, baijiaju1990, jjzuming, zzzccc427
SMP channels retain LTK pointers from the controller's long_term_keys
list until key notification or pairing teardown. load_long_term_keys()
clears that list under hdev->lock without invalidating the channels'
saved pointers. The SMP channel lock does not serialize the management
clear; kfree_rcu() does not keep a key alive for the rest of pairing.
An ongoing pairing and management LTK loads permit this ordering:
1. smp_cmd_initiator_ident() or sc_add_ltk() saves the hci_add_ltk()
result A in smp->ltk.
2. load_long_term_keys() takes hdev->lock, and hci_smp_ltks_clear()
unlinks A and queues its RCU free. The pairing retains its alias.
3. After the RCU grace period, A is freed. A later management load
allocates LTK B at the same address, possibly on another controller.
4. The old pairing reaches smp_chan_destroy() and reads smp->ltk->type
through the saved address.
If B is still live, the old pairing can read its type and remove it
through the stale alias; smp_notify_keys() can also overwrite and report
its fields. If B has already been freed, the access is a use-after-free.
The KASAN report below shows the latter ordering: the stale teardown
read follows allocation and RCU release of the replacement LTK.
Reject MGMT_OP_LOAD_LONG_TERM_KEYS with MGMT_STATUS_BUSY while LE or
BR/EDR ACL connections exist. Both transports can have SMP channels
retaining LTKs, including keys derived over BR/EDR. Check the connection
counts under hdev->lock and keep that lock through clearing and loading.
Connections are published under the same lock, and disconnect tears
down SMP before removing the connection from the hash. A successful
load therefore runs without pairing aliases, and a new connection
cannot acquire one until the replacement finishes.
KASAN report as below:
==================================================================
BUG: KASAN: slab-use-after-free in smp_chan_destroy.isra.0+0xc8b/0x1060
Read of size 1 at addr ffff88810a45eb28 by task kworker/u17:3/506
Workqueue: hci1 hci_rx_work
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? smp_chan_destroy.isra.0+0xc8b/0x1060
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? smp_chan_destroy.isra.0+0xc8b/0x1060
kasan_report+0xe0/0x110
? smp_chan_destroy.isra.0+0xc8b/0x1060
smp_chan_destroy.isra.0+0xc8b/0x1060
smp_teardown_cb+0xc7/0x230
l2cap_chan_del+0x123/0x8f0
l2cap_conn_del+0x31b/0x740
? reacquire_held_locks+0x1e7/0x1f0
? __pfx_l2cap_disconn_cfm+0x10/0x10
l2cap_disconn_cfm+0x87/0xd0
hci_disconn_complete_evt+0x30f/0x990
? srso_alias_return_thunk+0x5/0xfbef5
hci_event_packet+0x894/0xc70
? __pfx_hci_disconn_complete_evt+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_hci_event_packet+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? free_zapped_rcu+0xd0/0x1e0
? srso_alias_return_thunk+0x5/0xfbef5
? lockdep_hardirqs_on_prepare+0xea/0x1a0
? __pfx_hci_cmd_sync_complete+0x10/0x10
? trace_hardirqs_on+0x18/0x160
? srso_alias_return_thunk+0x5/0xfbef5
hci_rx_work+0x367/0xf00
? srso_alias_return_thunk+0x5/0xfbef5
process_one_work+0x908/0x19c0
? __pfx_process_one_work+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? lock_is_held_type+0x8f/0x100
? srso_alias_return_thunk+0x5/0xfbef5
worker_thread+0x65c/0xe40
? __pfx_worker_thread+0x10/0x10
kthread+0x34f/0x460
? srso_alias_return_thunk+0x5/0xfbef5
? __pfx_kthread+0x10/0x10
ret_from_fork+0x659/0x940
? __pfx_ret_from_fork+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __switch_to+0x74f/0xf80
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
Allocated by task 523:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x251/0x610
hci_add_ltk+0x285/0x4c0
load_long_term_keys+0x74a/0x8d0
hci_sock_sendmsg+0x128b/0x22e0
__sys_sendto+0x425/0x470
__x64_sys_sendto+0xe5/0x1c0
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 523:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
kasan_save_free_info+0x3b/0x60
__kasan_slab_free+0x5f/0x80
__rcu_free_sheaf_prepare+0x65/0x2a0
rcu_free_sheaf+0x1f/0xe0
rcu_core+0x661/0x1d10
handle_softirqs+0x201/0x930
__irq_exit_rcu+0x110/0x1e0
irq_exit_rcu+0xe/0x20
sysvec_apic_timer_interrupt+0x37/0x80
asm_sysvec_apic_timer_interrupt+0x1a/0x20
The buggy address belongs to the object at ffff88810a45eb00
which belongs to the cache kmalloc-96 of size 96
The buggy address is located 40 bytes inside of
freed 96-byte region [ffff88810a45eb00, ffff88810a45eb60)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0
pfn:0x10a45e
flags: 0x200000000000000(node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000000 ffff888100042280 dead000000000100
dead000000000122
raw: 0000000000000000 0000000000200020 00000000f5000000
0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810a45ea00: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
ffff88810a45ea80: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
>ffff88810a45eb00: fa fb fb fb fb fb fb fb fb fb fb fb fc fc fc fc
^
ffff88810a45eb80: 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc fc
ffff88810a45ec00: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc
==================================================================
Fixes: 346af67b8d11 ("Bluetooth: Add MGMT handlers for dealing with SMP LTK's")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 9f65863e7457d..c3e8ca48a0337 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -7460,6 +7460,16 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
hci_dev_lock(hdev);
+ /*
+ * SMP over LE or BR/EDR may retain pointers to the current LTKs.
+ */
+ if (hci_conn_num(hdev, LE_LINK) || hci_conn_num(hdev, ACL_LINK)) {
+ err = mgmt_cmd_status(sk, hdev->id,
+ MGMT_OP_LOAD_LONG_TERM_KEYS,
+ MGMT_STATUS_BUSY);
+ goto unlock;
+ }
+
hci_smp_ltks_clear(hdev);
for (i = 0; i < key_count; i++) {
@@ -7513,6 +7523,7 @@ static int load_long_term_keys(struct sock *sk, struct hci_dev *hdev,
err = mgmt_cmd_complete(sk, hdev->id, MGMT_OP_LOAD_LONG_TERM_KEYS, 0,
NULL, 0);
+unlock:
hci_dev_unlock(hdev);
return err;
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-10-13 20:08 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-13 16:26 [PATCH] Bluetooth: MGMT: Reject LTK loads while connections exist Cen Zhang
2026-10-13 20:08 ` bluez.test.bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox