* [PATCH 0/4] Bluetooth: Keep borrowed keys inside RCU sections
@ 2026-10-29 13:12 Cen Zhang
2026-10-29 13:12 ` [PATCH 1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU Cen Zhang
` (3 more replies)
0 siblings, 4 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(), 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.
Two such users have already been fixed this way in bluetooth-next by
commit 817cbcab91fe ("Bluetooth: hci_core: Keep RCU protection through
LTK filtering") and commit 4b76fa362110 ("Bluetooth: hci_sync: Protect
IRK identity reads with RCU").
This series applies the same approach to the remaining users that run
without hdev->lock:
1/4 hci_find_link_key() checks the blocked-key list after leaving
RCU, as hci_find_ltk() did before 817cbcab91fe.
2/4 sc_generate_ltk() derives an LTK from a borrowed link key.
3/4 smp_ltk_encrypt() starts encryption with a borrowed LTK.
4/4 hci_le_add_resolve_list_sync() copies a borrowed IRK, as
hci_le_set_privacy_mode_sync() did before 4b76fa362110.
The other callers either hold hdev->lock, which the management handlers
also take, or only test whether a key exists.
Each change only extends a read-side section over code that does not
sleep. Every section ends before a synchronous HCI command or a
GFP_KERNEL allocation. The patches are independent of each other.
Cen Zhang (4):
Bluetooth: hci_core: Keep link-key filtering inside RCU
Bluetooth: SMP: Protect link keys during LTK derivation
Bluetooth: SMP: Protect LTK encryption with RCU
Bluetooth: hci_sync: Copy the resolving-list IRK under RCU
net/bluetooth/hci_core.c | 5 ++---
net/bluetooth/hci_sync.c | 11 +++++++++--
net/bluetooth/smp.c | 29 +++++++++++++++++++++--------
3 files changed, 32 insertions(+), 13 deletions(-)
---
^ permalink raw reply [flat|nested] 6+ messages in thread
* [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 14:50 ` Bluetooth: Keep borrowed keys inside RCU sections bluez.test.bot
2026-10-29 13:12 ` [PATCH 2/4] Bluetooth: SMP: Protect link keys during LTK derivation Cen Zhang
` (2 subsequent siblings)
3 siblings, 1 reply; 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 related [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
2026-10-29 13:12 ` [PATCH 4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU Cen Zhang
3 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 related [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
3 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 related [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
3 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 related [flat|nested] 6+ messages in thread
* RE: Bluetooth: Keep borrowed keys inside RCU sections
2026-10-29 13:12 ` [PATCH 1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU Cen Zhang
@ 2026-10-29 14:50 ` bluez.test.bot
0 siblings, 0 replies; 6+ messages in thread
From: bluez.test.bot @ 2026-10-29 14:50 UTC (permalink / raw)
To: linux-bluetooth, zzzccc427
[-- Attachment #1: Type: text/plain, Size: 6288 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1196273/
---Test result---
Test Summary:
CheckPatch FAIL 5.59 seconds
VerifyFixes PASS 0.13 seconds
VerifySignedoff PASS 0.13 seconds
GitLint FAIL 1.37 seconds
SubjectPrefix PASS 0.48 seconds
BuildKernel PASS 32.31 seconds
CheckAllWarning PASS 36.86 seconds
CheckSparse PASS 40.06 seconds
BuildKernel32 PASS 32.70 seconds
CheckKernelLLVM PASS 37.40 seconds
TestRunnerSetup PASS 759.22 seconds
TestRunner_l2cap-tester PASS 12.84 seconds
TestRunner_iso-tester PASS 13.00 seconds
TestRunner_bnep-tester PASS 3.22 seconds
TestRunner_mgmt-tester PASS 88.44 seconds
TestRunner_rfcomm-tester PASS 4.58 seconds
TestRunner_sco-tester PASS 5.73 seconds
TestRunner_ioctl-tester PASS 4.74 seconds
TestRunner_mesh-tester PASS 5.61 seconds
TestRunner_smp-tester PASS 4.43 seconds
TestRunner_userchan-tester PASS 3.28 seconds
TestRunner_6lowpan-tester PASS 4.47 seconds
IncrementalBuild PASS 40.49 seconds
Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU
WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#289:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10b5f8
total: 0 errors, 1 warnings, 0 checks, 19 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluetooth-next/bluetooth-next/src/patch/14924412.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[2/4] Bluetooth: SMP: Protect link keys during LTK derivation
WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#272:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10618e
total: 0 errors, 1 warnings, 0 checks, 36 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluetooth-next/bluetooth-next/src/patch/14924414.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[3/4] Bluetooth: SMP: Protect LTK encryption with RCU
WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#171:
kfence-#128: 0x00000000d45af9b0-0x0000000053c1365e, size=72, cache=kmalloc-96
total: 0 errors, 1 warnings, 0 checks, 33 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluetooth-next/bluetooth-next/src/patch/14924413.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
[4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU
WARNING: Prefer a maximum 75 chars per line (possible unwrapped commit description?)
#248:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10d214
total: 0 errors, 1 warnings, 0 checks, 25 lines checked
NOTE: For some of the reported defects, checkpatch may be able to
mechanically convert to the typical style using --fix or --fix-inplace.
/home/runner/work/bluetooth-next/bluetooth-next/src/patch/14924415.patch has style problems, please review.
NOTE: Ignored message types: UNKNOWN_COMMIT_ID
NOTE: If any of the errors are false positives, please report
them to the maintainer, see CHECKPATCH in MAINTAINERS.
##############################
Test: GitLint - FAIL
Desc: Run gitlint
Output:
[1/4] Bluetooth: hci_core: Keep link-key filtering inside RCU
44: B1 Line exceeds max length (107>80): " CPU: 1 UID: 0 PID: 502 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #5 PREEMPT(lazy)"
45: B1 Line exceeds max length (119>80): " 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"
[2/4] Bluetooth: SMP: Protect link keys during LTK derivation
39: B1 Line exceeds max length (107>80): " CPU: 0 UID: 0 PID: 500 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy)"
40: B1 Line exceeds max length (119>80): " 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"
[3/4] Bluetooth: SMP: Protect LTK encryption with RCU
48: B1 Line exceeds max length (81>80): " kfence-#128: 0x00000000d45af9b0-0x0000000053c1365e, size=72, cache=kmalloc-96"
80: B1 Line exceeds max length (109>80): " CPU: 2 UID: 0 PID: 534 Comm: l2cap-ltk-actor Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #5 PREEMPT(lazy)"
81: B1 Line exceeds max length (119>80): " 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"
[4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU
41: B1 Line exceeds max length (107>80): " CPU: 2 UID: 0 PID: 504 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #2 PREEMPT(lazy)"
42: B1 Line exceeds max length (119>80): " 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"
https://github.com/bluez/bluetooth-next/pull/1023
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-29 14:50 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 14:50 ` Bluetooth: Keep borrowed keys inside RCU sections bluez.test.bot
2026-10-29 13:12 ` [PATCH 2/4] Bluetooth: SMP: Protect link keys during LTK derivation Cen Zhang
2026-10-29 13:12 ` [PATCH 3/4] Bluetooth: SMP: Protect LTK encryption with RCU Cen Zhang
2026-10-29 13:12 ` [PATCH 4/4] Bluetooth: hci_sync: Copy the resolving-list IRK under RCU Cen Zhang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox