From 817cbcab91fe870e5b69c835cbd708242c9f8c8e Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Tue, 6 Oct 2026 15:38:38 +0800 Subject: [PATCH] Bluetooth: hci_core: Keep RCU protection through LTK filtering The LTK selected by hci_find_ltk() must remain allocated while its value is checked against the blocked-key list. The finder releases RCU before passing k->val to hci_is_blocked_key(). The helper's own RCU section protects the blocked-key list, but the selected LTK may already have been reclaimed before that section starts. An LE ATT socket security request can reach smp_ltk_encrypt() without holding hdev->lock. With a matching stored LTK and an LTK-type blocked-key entry present, a concurrent MGMT_OP_LOAD_LONG_TERM_KEYS can remove the selected LTK in the following order: Socket security Management / RCU reclamation hci_find_ltk() rcu_read_lock() select k rcu_read_unlock() load_long_term_keys() hci_dev_lock(hdev) hci_smp_ltks_clear(hdev) list_del_rcu(&k->list) kfree_rcu(k, rcu) grace period ends; k is freed hci_is_blocked_key(..., k->val) memcmp(b->val, val, ...) The final comparison reads freed LTK bytes. Entering a new RCU section in hci_is_blocked_key() cannot protect a pointer obtained before the completed grace period. Keep the finder's RCU read-side section active through the blocked-key comparison and the warning that reads k->bdaddr. Release it before returning on either matched-key path. This delays reclamation until the finder has finished using the selected record, following the existing IRK lookup pattern. KFENCE report as below: ================================================================== BUG: KFENCE: use-after-free read in hci_is_blocked_key+0xdf/0x240 Use-after-free read at 0x00000000b9ee38e4 (in kfence-#249): hci_is_blocked_key+0xdf/0x240 blocked_key_wrapper+0xc5/0x140 [pmbd_ltk_probe] hci_find_ltk+0x1aa/0x3e0 smp_ltk_encrypt.isra.0+0xad/0x340 smp_conn_security+0x3b8/0x8c0 l2cap_sock_setsockopt+0x1c4b/0x2270 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-#249: 0x00000000121f2ad9-0x000000005b5bfb3d, size=72, cache=kmalloc-96 allocated by task 495 on cpu 0 at 36.722380s (2.603973s ago): hci_add_ltk+0x1f5/0x3a0 load_long_term_keys+0x6d0/0x860 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 533 on cpu 0 at 36.993097s (2.339769s ago): __rcu_free_sheaf_prepare+0x261/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 srso_alias_return_thunk+0x0/0xfbef5 __asan_memcpy+0x23/0x60 create_monitor_ctrl_event+0x1a2/0x460 mgmt_cmd_complete+0x2da/0x580 load_long_term_keys+0x741/0x860 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 CPU: 1 UID: 0 PID: 495 Comm: python3 Tainted: G O 7.2.0-rc6-pmb-bt-functional-v1+ #1 PREEMPT(lazy) Tainted: [O]=OOT_MODULE 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: 600a87490ff9 ("Bluetooth: Implementation of MGMT_OP_SET_BLOCKED_KEYS.") Assisted-by: LLM Signed-off-by: Cen Zhang Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index c8b57848228b..209244fd9391 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1153,16 +1153,16 @@ struct smp_ltk *hci_find_ltk(struct hci_dev *hdev, bdaddr_t *bdaddr, continue; if (smp_ltk_is_sc(k) || ltk_role(k->type) == role) { - rcu_read_unlock(); - if (hci_is_blocked_key(hdev, HCI_BLOCKED_KEY_TYPE_LTK, k->val)) { bt_dev_warn_ratelimited(hdev, "LTK blocked for %pMR", &k->bdaddr); + rcu_read_unlock(); return NULL; } + rcu_read_unlock(); return k; } }