* [PATCH 1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU
2026-10-29 13:12 [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections Cen Zhang
@ 2026-10-29 13:12 ` Cen Zhang
2026-10-29 13:12 ` [PATCH 2/4] Bluetooth: SMP: Protect link keys during LTK derivation Cen Zhang
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Cen Zhang @ 2026-10-29 13:12 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: linux-bluetooth, linux-kernel, baijiaju1990, jjzuming, zzzccc427
hci_find_link_key() must keep the matching link key allocated while it
checks the key against the blocked-key list. It currently leaves the
key-list RCU read-side section before passing k->val to
hci_is_blocked_key(). That helper's own RCU section protects the
blocked-key list, but it cannot keep an unlinked link key from being
reclaimed.
The SMP receive path calls the finder without hdev->lock: Secure
Connections key distribution over BR/EDR reaches it through
smp_distribute_keys() and sc_generate_ltk(). With a stored peer link
key and a link-key entry in the blocked-key list,
MGMT_OP_LOAD_LINK_KEYS can free the selected key before the comparison:
HCI RX worker (SMP) Management / RCU reclamation
hci_find_link_key()
rcu_read_lock()
select k
rcu_read_unlock()
load_link_keys()
hci_dev_lock(hdev)
hci_link_keys_clear(hdev)
list_del_rcu(), kfree_rcu()
hci_dev_unlock(hdev)
grace period ends, k is freed
hci_is_blocked_key(hdev, ..., k->val)
memcmp() reads the freed key
Keep the RCU read-side section active through the blocked-key
comparison and the warning that prints k->bdaddr, and release it before
returning on both matched-key paths. hci_find_ltk() already handles its
blocked-key check this way.
KASAN reported this on a test kernel whose scheduling hook paused the
receive worker after the lookup until the key had been freed.
KASAN report as below:
==================================================================
BUG: KASAN: slab-use-after-free in hci_is_blocked_key+0x2db/0x300
Read of size 1 at addr ffff88810b5f89a7 by task kworker/u17:0/502
CPU: 1 UID: 0 PID: 502 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #5 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: hci1 hci_rx_work
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? hci_is_blocked_key+0x2db/0x300
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? hci_is_blocked_key+0x2db/0x300
kasan_report+0xe0/0x110
? hci_is_blocked_key+0x2db/0x300
hci_is_blocked_key+0x2db/0x300
hci_find_link_key+0x12e/0x400
smp_distribute_keys+0x1830/0x2950
? trace_hardirqs_on+0x18/0x160
? srso_alias_return_thunk+0x5/0xfbef5
? queue_delayed_work_on+0xb3/0xf0
? __pfx_smp_distribute_keys+0x10/0x10
? __pfx_smp_send_cmd.isra.0+0x10/0x10
? __cancel_work+0x1b0/0x290
? __entry_text_end+0x1020f7/0x1020f9
? lockdep_hardirqs_on_prepare+0xea/0x1a0
? srso_alias_return_thunk+0x5/0xfbef5
? __asan_memset+0x23/0x50
? smp_recv_cb+0x12c7/0x8d80
? srso_alias_return_thunk+0x5/0xfbef5
smp_recv_cb+0x12c7/0x8d80
? __pfx_smp_recv_cb+0x10/0x10
? __pfx___might_resched+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? stack_depot_save_flags+0x29/0x9a0
? srso_alias_return_thunk+0x5/0xfbef5
? __mutex_lock+0x182c/0x2590
? kasan_save_stack+0x42/0x60
? l2cap_recv_frame+0x9ef/0x8830
? __kasan_slab_free+0x5f/0x80
? kmem_cache_free+0x242/0x580
? kfree_skbmem+0x15e/0x1d0
? sk_skb_reason_drop+0x12f/0x490
? hci_event_packet+0x556/0xc70
? hci_rx_work+0x367/0xf00
? process_one_work+0x908/0x19c0
? worker_thread+0x65c/0xe40
? __pfx___mutex_lock+0x10/0x10
? __lock_acquire+0x466/0x2260
? __pfx_l2cap_chan_hold_unless_zero+0x10/0x10
? l2cap_recv_frame+0x13cc/0x8830
? __pfx_smp_recv_cb+0x10/0x10
l2cap_recv_frame+0x13cc/0x8830
? srso_alias_return_thunk+0x5/0xfbef5
? __mutex_lock+0x182c/0x2590
? __pfx_l2cap_recv_frame+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? find_held_lock+0x2b/0x80
? l2cap_recv_acldata+0x1eb/0x10e0
? __pfx___mutex_lock+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __mutex_unlock_slowpath+0x147/0x920
? __pfx_l2cap_conn_hold_unless_zero+0x10/0x10
? __pfx_hci_conn_enter_active_mode+0x10/0x10
? l2cap_recv_acldata+0x13d/0x10e0
? srso_alias_return_thunk+0x5/0xfbef5
l2cap_recv_acldata+0xe9f/0x10e0
? srso_alias_return_thunk+0x5/0xfbef5
hci_rx_work+0x5a6/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 504:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x251/0x610
hci_add_link_key+0x474/0x840
hci_link_key_notify_evt+0x492/0xce0
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
Freed by task 29:
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_nobarn+0x1d/0x50
rcu_core+0x661/0x1d10
handle_softirqs+0x201/0x930
run_ksoftirqd+0x33/0x60
smpboot_thread_fn+0x3bd/0x9a0
kthread+0x34f/0x460
ret_from_fork+0x659/0x940
ret_from_fork_asm+0x1a/0x30
The buggy address belongs to the object at ffff88810b5f8980
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 39 bytes inside of
freed 64-byte region [ffff88810b5f8980, ffff88810b5f89c0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10b5f8
flags: 0x200000000000000(node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000000 ffff8881000428c0 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810b5f8880: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88810b5f8900: 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc fc
>ffff88810b5f8980: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88810b5f8a00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88810b5f8a80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
==================================================================
Fixes: 600a87490ff9 ("Bluetooth: Implementation of MGMT_OP_SET_BLOCKED_KEYS.")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
net/bluetooth/hci_core.c | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 2a29cfdee9728..70778a4594e31 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -1170,17 +1170,16 @@ struct link_key *hci_find_link_key(struct hci_dev *hdev, bdaddr_t *bdaddr)
rcu_read_lock();
list_for_each_entry_rcu(k, &hdev->link_keys, list) {
if (bacmp(bdaddr, &k->bdaddr) == 0) {
- rcu_read_unlock();
-
if (hci_is_blocked_key(hdev,
HCI_BLOCKED_KEY_TYPE_LINKKEY,
k->val)) {
bt_dev_warn_ratelimited(hdev,
"Link key blocked for %pMR",
&k->bdaddr);
- return NULL;
+ k = NULL;
}
+ rcu_read_unlock();
return k;
}
}
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 2/4] Bluetooth: SMP: Protect link keys during LTK derivation
2026-10-29 13:12 [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections Cen Zhang
2026-10-29 13:12 ` [PATCH 1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU Cen Zhang
@ 2026-10-29 13:12 ` Cen Zhang
2026-10-29 13:12 ` [PATCH 3/4] Bluetooth: SMP: Protect LTK encryption with RCU Cen Zhang
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Cen Zhang @ 2026-10-29 13:12 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: linux-bluetooth, linux-kernel, baijiaju1990, jjzuming, zzzccc427
sc_generate_ltk() reads the type and value of the peer link key after
hci_find_link_key() has returned. The finder ends its RCU read-side
section before returning the borrowed entry, and the SMP receive path
does not hold hdev->lock: l2cap_recv_acldata() releases it before the
PDU is dispatched. The L2CAP connection and channel locks held by SMP
do not serialize with management link-key removal.
When BR/EDR Secure Connections pairing derives an LTK from the stored
link key, MGMT_OP_LOAD_LINK_KEYS can free that key mid-derivation:
HCI RX worker (SMP) Management / RCU reclamation
sc_generate_ltk()
key = hci_find_link_key()
load_link_keys()
hci_dev_lock(hdev)
hci_link_keys_clear(hdev)
list_del_rcu(), kfree_rcu()
hci_dev_unlock(hdev)
grace period ends, key is freed
read key->type
smp_h6() or smp_h7() reads key->val
Hold an RCU read-side section from the lookup until the type and value
have been consumed. smp_h6() and smp_h7() only run the synchronous
AES-CMAC library code, which neither sleeps nor allocates. Release the
section before sc_add_ltk(), which allocates with GFP_KERNEL.
KASAN reported this on a test kernel whose scheduling hook paused the
receive worker after the lookup; the hook frames appear in the trace.
KASAN report as below:
==================================================================
BUG: KASAN: slab-use-after-free in smp_distribute_keys+0x2983/0x2b70
Read of size 1 at addr ffff88810618e9a6 by task kworker/u17:0/500
CPU: 0 UID: 0 PID: 500 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: hci1 hci_rx_work
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? smp_distribute_keys+0x2983/0x2b70
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? smp_distribute_keys+0x2983/0x2b70
kasan_report+0xe0/0x110
? smp_distribute_keys+0x2983/0x2b70
smp_distribute_keys+0x2983/0x2b70
? __pfx_smp_distribute_keys+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? pmbd_gate_maybe+0x47/0x60
? srso_alias_return_thunk+0x5/0xfbef5
? pmbd_probe_hit_cookie+0xe7/0x1c0
? srso_alias_return_thunk+0x5/0xfbef5
? pmbd_probe_hit_cookie+0xee/0x1c0
? __pfx_pmbd_probe_hit_cookie+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __asan_memset+0x23/0x50
? smp_recv_cb+0x12e2/0x8f30
? srso_alias_return_thunk+0x5/0xfbef5
smp_recv_cb+0x12e2/0x8f30
? __pfx_smp_recv_cb+0x10/0x10
? __pfx___might_resched+0x10/0x10
? __pfx_sched_balance_find_src_group+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __mutex_lock+0x182c/0x2590
? l2cap_recv_frame+0x9ef/0x8830
? __pfx___mutex_lock+0x10/0x10
? __lock_acquire+0x466/0x2260
? __pfx_l2cap_chan_hold_unless_zero+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? l2cap_recv_frame+0x13cc/0x8830
? __pfx_smp_recv_cb+0x10/0x10
l2cap_recv_frame+0x13cc/0x8830
? srso_alias_return_thunk+0x5/0xfbef5
? __mutex_lock+0x182c/0x2590
? __pfx_l2cap_recv_frame+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? find_held_lock+0x2b/0x80
? l2cap_recv_acldata+0x1eb/0x10e0
? __pfx___mutex_lock+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? __mutex_unlock_slowpath+0x147/0x920
? __pfx_l2cap_conn_hold_unless_zero+0x10/0x10
? __pfx_hci_conn_enter_active_mode+0x10/0x10
? l2cap_recv_acldata+0x13d/0x10e0
? srso_alias_return_thunk+0x5/0xfbef5
l2cap_recv_acldata+0xe9f/0x10e0
? srso_alias_return_thunk+0x5/0xfbef5
hci_rx_work+0x5a6/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 500:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x251/0x610
hci_add_link_key+0x474/0x840
hci_link_key_notify_evt+0x492/0xce0
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
Freed by task 0:
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+0x6c/0x80
asm_sysvec_apic_timer_interrupt+0x1a/0x20
The buggy address belongs to the object at ffff88810618e980
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 38 bytes inside of
freed 64-byte region [ffff88810618e980, ffff88810618e9c0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10618e
flags: 0x200000000000000(node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000000 ffff8881000428c0 dead000000000122 0000000000000000
raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810618e880: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88810618e900: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>ffff88810618e980: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88810618ea00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88810618ea80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
==================================================================
Fixes: b5ae344d4c0f ("Bluetooth: Add full SMP BR/EDR support")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
net/bluetooth/smp.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 984b2d0b5e3e9..1d3f3ceb3ecc8 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -1186,9 +1186,13 @@ static void sc_generate_ltk(struct smp_chan *smp)
struct hci_conn *hcon = smp->conn->hcon;
struct hci_dev *hdev = hcon->hdev;
struct link_key *key;
+ int err;
+
+ rcu_read_lock();
key = hci_find_link_key(hdev, &hcon->dst);
if (!key) {
+ rcu_read_unlock();
bt_dev_err(hdev, "no Link Key found to generate LTK");
return;
}
@@ -1200,16 +1204,19 @@ static void sc_generate_ltk(struct smp_chan *smp)
/* SALT = 0x000000000000000000000000746D7032 */
const u8 salt[16] = { 0x32, 0x70, 0x6d, 0x74 };
- if (smp_h7(key->val, salt, smp->tk))
- return;
+ err = smp_h7(key->val, salt, smp->tk);
} else {
/* From core spec. Spells out in ASCII as 'tmp2'. */
const u8 tmp2[4] = { 0x32, 0x70, 0x6d, 0x74 };
- if (smp_h6(key->val, tmp2, smp->tk))
- return;
+ err = smp_h6(key->val, tmp2, smp->tk);
}
+ rcu_read_unlock();
+
+ if (err)
+ return;
+
if (smp_h6(smp->tk, brle, smp->tk))
return;
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/4] Bluetooth: SMP: Protect LTK encryption with RCU
2026-10-29 13:12 [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections Cen Zhang
2026-10-29 13:12 ` [PATCH 1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU Cen Zhang
2026-10-29 13:12 ` [PATCH 2/4] Bluetooth: SMP: Protect link keys during LTK derivation Cen Zhang
@ 2026-10-29 13:12 ` Cen Zhang
2026-10-29 13:12 ` [PATCH 4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU Cen Zhang
2026-10-30 14:00 ` [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections patchwork-bot+bluetooth
4 siblings, 0 replies; 6+ messages in thread
From: Cen Zhang @ 2026-10-29 13:12 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: linux-bluetooth, linux-kernel, baijiaju1990, jjzuming, zzzccc427
smp_ltk_encrypt() checks the security level of the LTK returned by
hci_find_ltk() and then starts encryption with its EDIV, Rand, value
and key size. The finder ends its RCU read-side section before
returning the borrowed entry, and neither caller holds hdev->lock: the
SMP Security Request handler runs from the receive path, and
setsockopt(BT_SECURITY) reaches smp_conn_security() with only the
socket and channel locks held.
MGMT_OP_LOAD_LONG_TERM_KEYS can therefore free the LTK in between:
Socket security request Management / RCU reclamation
smp_ltk_encrypt()
key = hci_find_ltk()
load_long_term_keys()
hci_dev_lock(hdev)
hci_smp_ltks_clear(hdev)
list_del_rcu(), kfree_rcu()
hci_dev_unlock(hdev)
grace period ends, key is freed
smp_ltk_sec_level(key)
hci_le_start_enc(hcon, key->ediv, key->rand, key->val, ...)
Keep an RCU read-side section across the lookup and every use of the
key. hci_le_start_enc() copies the key into the command and queues it
with a GFP_ATOMIC allocation, so the whole sequence can stay inside the
section.
KFENCE reported this on a test kernel where the socket thread was
paused after the lookup until the RCU callback had freed the key.
KFENCE report as below:
==================================================================
BUG: KFENCE: use-after-free read in smp_ltk_encrypt.isra.0+0xf3/0x370
Use-after-free read at 0x00000000c45332eb (in kfence-#128):
smp_ltk_encrypt.isra.0+0xf3/0x370
smp_conn_security+0x489/0x9d0
l2cap_sock_setsockopt+0x1bbe/0x2300
do_sock_setsockopt+0x1fb/0x430
__sys_setsockopt+0x106/0x180
__x64_sys_setsockopt+0xc2/0x160
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
kfence-#128: 0x00000000d45af9b0-0x0000000053c1365e, size=72, cache=kmalloc-96
allocated by task 533 on cpu 0 at 50.484258s (0.449177s ago):
hci_add_ltk+0x1f5/0x3a0
load_long_term_keys+0x6ea/0x930
hci_sock_sendmsg+0x128b/0x22e0
sock_write_iter+0x4c0/0x550
vfs_write+0x637/0x1010
ksys_write+0x1b7/0x200
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
freed by task 0 on cpu 0 at 50.846944s (0.093664s ago):
__rcu_free_sheaf_prepare+0x261/0x2a0
rcu_free_sheaf_nobarn+0x1d/0x50
rcu_core+0x661/0x1d10
handle_softirqs+0x201/0x930
__irq_exit_rcu+0x110/0x1e0
irq_exit_rcu+0xe/0x20
sysvec_apic_timer_interrupt+0x6c/0x80
asm_sysvec_apic_timer_interrupt+0x1a/0x20
pv_native_safe_halt+0xf/0x20
default_idle+0x9/0x10
default_idle_call+0x6c/0xb0
do_idle+0x3a7/0x650
cpu_startup_entry+0x54/0x60
rest_init+0x15e/0x2b0
start_kernel+0x412/0x4b0
x86_64_start_reservations+0x18/0x30
x86_64_start_kernel+0x111/0x120
common_startup_64+0x13e/0x158
CPU: 2 UID: 0 PID: 534 Comm: l2cap-ltk-actor Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #5 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
==================================================================
Fixes: 988c5997d320 ("Bluetooth: Use the LTK after receiving a LE Security Request")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
net/bluetooth/smp.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c
index 1d3f3ceb3ecc8..b7015e37e1f1c 100644
--- a/net/bluetooth/smp.c
+++ b/net/bluetooth/smp.c
@@ -2209,16 +2209,20 @@ static bool smp_ltk_encrypt(struct l2cap_conn *conn, u8 sec_level)
{
struct smp_ltk *key;
struct hci_conn *hcon = conn->hcon;
+ bool ret = false;
+
+ rcu_read_lock();
key = hci_find_ltk(hcon->hdev, &hcon->dst, hcon->dst_type, hcon->role);
if (!key)
- return false;
+ goto unlock;
if (smp_ltk_sec_level(key) < sec_level)
- return false;
+ goto unlock;
+ ret = true;
if (test_and_set_bit(HCI_CONN_ENCRYPT_PEND, hcon->flags))
- return true;
+ goto unlock;
hci_le_start_enc(hcon, key->ediv, key->rand, key->val, key->enc_size);
hcon->enc_key_size = key->enc_size;
@@ -2226,7 +2230,9 @@ static bool smp_ltk_encrypt(struct l2cap_conn *conn, u8 sec_level)
/* We never store STKs for initiator role, so clear this flag */
clear_bit(HCI_CONN_STK_ENCRYPT, hcon->flags);
- return true;
+unlock:
+ rcu_read_unlock();
+ return ret;
}
bool smp_sufficient_security(struct hci_conn *hcon, u8 sec_level,
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU
2026-10-29 13:12 [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections Cen Zhang
` (2 preceding siblings ...)
2026-10-29 13:12 ` [PATCH 3/4] Bluetooth: SMP: Protect LTK encryption with RCU Cen Zhang
@ 2026-10-29 13:12 ` Cen Zhang
2026-10-30 14:00 ` [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections patchwork-bot+bluetooth
4 siblings, 0 replies; 6+ messages in thread
From: Cen Zhang @ 2026-10-29 13:12 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: linux-bluetooth, linux-kernel, baijiaju1990, jjzuming, zzzccc427
hci_le_add_resolve_list_sync() copies the peer IRK into the LE Add
Device To Resolving List command after hci_find_irk_by_addr() has
returned. The finder ends its RCU read-side section before returning
the borrowed entry, and neither the request worker nor
interleave_scan_work() holds hdev->lock here. The resolving-list
membership check between the lookup and the copy takes hdev->lock and
may sleep on it, which widens the window.
When LL privacy programs a peer with address resolution enabled,
MGMT_OP_LOAD_IRKS can free its IRK before the copy:
Request worker Management / RCU reclamation
hci_le_add_resolve_list_sync()
irk = hci_find_irk_by_addr()
load_irks()
hci_dev_lock(hdev)
hci_smp_irks_clear(hdev)
list_del_rcu(), kfree_rcu()
hci_dev_unlock(hdev)
grace period ends, irk is freed
resolving-list membership check
memcpy(cp.peer_irk, irk->val, 16)
Copy the IRK value in an RCU read-side section right after the lookup,
as hci_le_set_privacy_mode_sync() does for the identity address. The
section ends before hdev->lock is taken and before the synchronous HCI
command is sent.
KASAN reported this on a test kernel with scheduling hooks, whose
frames appear in the trace. The report predates the hdev->lock taken
by the membership check.
KASAN report as below:
==================================================================
BUG: KASAN: slab-use-after-free in hci_le_add_resolve_list_sync+0x8d9/0x910
Read of size 16 at addr ffff88810d21452d by task kworker/u17:0/504
CPU: 2 UID: 0 PID: 504 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #2 PREEMPT(lazy)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: hci0 interleave_scan_work
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? hci_le_add_resolve_list_sync+0x8d9/0x910
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? hci_le_add_resolve_list_sync+0x8d9/0x910
kasan_report+0xe0/0x110
? hci_le_add_resolve_list_sync+0x8d9/0x910
hci_le_add_resolve_list_sync+0x8d9/0x910
? __pfx_hci_le_add_resolve_list_sync+0x10/0x10
? pmbd_probe_hit_cookie+0xe7/0x1c0
? srso_alias_return_thunk+0x5/0xfbef5
? pmbd_probe_hit_cookie+0xee/0x1c0
? __pfx_pmbd_probe_hit_cookie+0x10/0x10
hci_le_add_accept_list_sync+0x18e/0x820
? conn_params_copy+0x36e/0x650
? __pfx_hci_le_add_accept_list_sync+0x10/0x10
? srso_alias_return_thunk+0x5/0xfbef5
? conn_params_copy+0x3a0/0x650
hci_passive_scan_sync+0x95f/0x1f10
? __pfx_hci_passive_scan_sync+0x10/0x10
? debug_object_deactivate+0x2f8/0x3c0
? pwq_dec_nr_in_flight+0x880/0xdf0
? srso_alias_return_thunk+0x5/0xfbef5
? lock_acquire+0x17b/0x2f0
? process_one_work+0x895/0x19c0
interleave_scan_work+0x11b/0x470
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 535:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x251/0x610
hci_add_irk+0xb6/0x220
load_irks+0x5f9/0x840
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 0:
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+0x6c/0x80
asm_sysvec_apic_timer_interrupt+0x1a/0x20
The buggy address belongs to the object at ffff88810d214500
which belongs to the cache kmalloc-64 of size 64
The buggy address is located 45 bytes inside of
freed 64-byte region [ffff88810d214500, ffff88810d214540)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10d214
flags: 0x200000000000000(node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000000 ffff8881000428c0 dead000000000100 dead000000000122
raw: 0000000000000000 0000000000200020 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810d214400: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88810d214480: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
>ffff88810d214500: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
^
ffff88810d214580: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
ffff88810d214600: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc
==================================================================
Fixes: e8907f76544f ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 3")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
net/bluetooth/hci_sync.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c
index 9d71ce96bd8ec..9d829c20c3160 100644
--- a/net/bluetooth/hci_sync.c
+++ b/net/bluetooth/hci_sync.c
@@ -2533,9 +2533,17 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
} else if (!(params->flags & HCI_CONN_FLAG_ADDRESS_RESOLUTION))
return 0;
+ rcu_read_lock();
+
irk = hci_find_irk_by_addr(hdev, ¶ms->addr, params->addr_type);
- if (!irk)
+ if (!irk) {
+ rcu_read_unlock();
return 0;
+ }
+
+ memcpy(cp.peer_irk, irk->val, 16);
+
+ rcu_read_unlock();
/* Check if the IK has _not_ been programmed yet. */
hci_dev_lock(hdev);
@@ -2547,7 +2555,6 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev,
cp.bdaddr_type = params->addr_type;
bacpy(&cp.bdaddr, ¶ms->addr);
- memcpy(cp.peer_irk, irk->val, 16);
/* Default privacy mode is always Network */
params->privacy_mode = HCI_NETWORK_PRIVACY;
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections
2026-10-29 13:12 [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections Cen Zhang
` (3 preceding siblings ...)
2026-10-29 13:12 ` [PATCH 4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU Cen Zhang
@ 2026-10-30 14:00 ` patchwork-bot+bluetooth
4 siblings, 0 replies; 6+ messages in thread
From: patchwork-bot+bluetooth @ 2026-10-30 14:00 UTC (permalink / raw)
To: Cen Zhang
Cc: marcel, luiz.dentz, linux-bluetooth, linux-kernel, baijiaju1990,
jjzuming
Hello:
This series was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Thu, 29 Oct 2026 21:12:25 +0800 you wrote:
> hci_find_link_key(), hci_find_ltk() and hci_find_irk_by_addr() return
> entries of RCU-protected lists after leaving their read-side sections.
> Management clears and reloads these lists under hdev->lock, and
> kfree_rcu() frees the old entries after a grace period. A user that
> does not hold hdev->lock must therefore keep the returned entry inside
> its own RCU read-side section until it has finished reading it.
>
> [...]
Here is the summary with links:
- [1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU
https://git.kernel.org/bluetooth/bluetooth-next/c/b5bcffa76cbe
- [2/4] Bluetooth: SMP: Protect link keys during LTK derivation
https://git.kernel.org/bluetooth/bluetooth-next/c/a7a9d27def4b
- [3/4] Bluetooth: SMP: Protect LTK encryption with RCU
https://git.kernel.org/bluetooth/bluetooth-next/c/5b9045b0a114
- [4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU
https://git.kernel.org/bluetooth/bluetooth-next/c/c278da2c4761
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 6+ messages in thread