From f05bd61a3213613de7848c8df2865492d5225023 Mon Sep 17 00:00:00 2001 From: Cen Zhang Date: Thu, 22 Oct 2026 20:05:56 +0800 Subject: [PATCH] Bluetooth: hci_sync: Protect resolving-list lookups The host resolving list is updated under hdev->lock, and each bdaddr_list_with_irk entry must remain alive throughout a lookup. hci_le_del_resolve_list_sync() and hci_le_add_resolve_list_sync() traverse that list without taking the lock. On an LE privacy-capable controller with a programmed entry, passive scan reconciliation can reach hci_le_del_resolve_list_sync() while a privileged HCI_RAW client sends LE Clear Resolving List. The raw command does not take req_lock, and its successful completion runs on the controller workqueue independently of request work: Request work Raw HCI / event work hci_le_del_resolve_list_sync() lookup loads a list node hci_sock_sendmsg() queue LE Clear Resolving List hci_cc_le_clear_resolv_list() take hdev->lock unlink and free the node release hdev->lock read the node's address/type The clear can free the node after the lookup loads it and before its address/type read, causing a use-after-free. The report below shows this access with the delete helper on the faulting stack. The add helper uses the same unprotected lookup. Take hdev->lock around each resolving-list membership lookup and retain only a boolean result. This serializes traversal with clear completion and keeps entry pointers inside the protected section. Release the lock before issuing a synchronous HCI command, whose completion also needs the lock. KASAN report as below: ================================================================== BUG: KASAN: slab-use-after-free in hci_bdaddr_list_lookup_with_irk+0x106/0x120 Read of size 1 at addr ffff88810b036e16 by task kworker/u17:0/495 CPU: 2 UID: 0 PID: 495 Comm: kworker/u17:0 Not tainted 7.2.0-rc6-pmb-bt-functional-v1+ #3 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 hci_cmd_sync_work Call Trace: dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? hci_bdaddr_list_lookup_with_irk+0x106/0x120 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? hci_bdaddr_list_lookup_with_irk+0x106/0x120 kasan_report+0xe0/0x110 ? hci_bdaddr_list_lookup_with_irk+0x106/0x120 hci_bdaddr_list_lookup_with_irk+0x106/0x120 hci_le_del_resolve_list_sync.isra.0+0xd6/0x3f0 ? __pfx_hci_le_del_resolve_list_sync.isra.0+0x10/0x10 ? srso_alias_return_thunk+0x5/0xfbef5 hci_le_del_accept_list_sync.isra.0+0x11c/0x290 ? __pfx_hci_le_del_accept_list_sync.isra.0+0x10/0x10 ? hci_passive_scan_sync+0x551/0x1e80 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x280 hci_passive_scan_sync+0x71c/0x1e80 ? __pfx_hci_passive_scan_sync+0x10/0x10 ? find_held_lock+0x2b/0x80 ? hci_lookup_le_connect+0xe1/0x3c0 ? srso_alias_return_thunk+0x5/0xfbef5 ? find_held_lock+0x2b/0x80 ? hci_update_passive_scan_sync+0x437/0x950 ? srso_alias_return_thunk+0x5/0xfbef5 ? lock_release+0xc8/0x280 hci_update_passive_scan_sync+0x461/0x950 hci_cmd_sync_work+0x1b3/0x450 ? 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 Allocated by task 495: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_cache_noprof+0x251/0x610 hci_bdaddr_list_add_with_irk+0x183/0x3e0 hci_cc_le_add_to_resolv_list+0x18c/0x240 hci_cmd_complete_evt+0x373/0xab0 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 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 hci_bdaddr_list_clear+0x134/0x230 hci_cc_le_clear_resolv_list+0x13d/0x240 hci_cmd_complete_evt+0x373/0xab0 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 The buggy address belongs to the object at ffff88810b036e00 which belongs to the cache kmalloc-64 of size 64 The buggy address is located 22 bytes inside of freed 64-byte region [ffff88810b036e00, ffff88810b036e40) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10b036 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: ffff88810b036d00: 00 00 00 00 00 00 00 00 fc fc fc fc fc fc fc fc ffff88810b036d80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc >ffff88810b036e00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ^ ffff88810b036e80: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ffff88810b036f00: fa fb fb fb fb fb fb fb fc fc fc fc fc fc fc fc ================================================================== Disabling lock debugging due to kernel taint Fixes: e8907f76544f ("Bluetooth: hci_sync: Make use of hci_cmd_sync_queue set 3") Assisted-by: LLM Signed-off-by: Cen Zhang Signed-off-by: Luiz Augusto von Dentz --- net/bluetooth/hci_sync.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index 4c090d2a6eed..9d71ce96bd8e 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -2448,15 +2448,17 @@ static int hci_le_del_resolve_list_sync(struct hci_dev *hdev, bdaddr_t *bdaddr, u8 bdaddr_type) { struct hci_cp_le_del_from_resolv_list cp; - struct bdaddr_list_with_irk *entry; + bool exists; if (!ll_privacy_capable(hdev)) return 0; /* Check if the IRK has been programmed */ - entry = hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, bdaddr, - bdaddr_type); - if (!entry) + hci_dev_lock(hdev); + exists = hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, + bdaddr, bdaddr_type); + hci_dev_unlock(hdev); + if (!exists) return 0; cp.bdaddr_type = bdaddr_type; @@ -2512,8 +2514,8 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev, { struct hci_cp_le_add_to_resolv_list cp; struct smp_irk *irk; - struct bdaddr_list_with_irk *entry; struct hci_conn_params *p; + bool exists; if (!ll_privacy_capable(hdev)) return 0; @@ -2536,10 +2538,11 @@ static int hci_le_add_resolve_list_sync(struct hci_dev *hdev, return 0; /* Check if the IK has _not_ been programmed yet. */ - entry = hci_bdaddr_list_lookup_with_irk(&hdev->le_resolv_list, - ¶ms->addr, - params->addr_type); - if (entry) + hci_dev_lock(hdev); + exists = hci_bdaddr_list_lookup_with_irk( + &hdev->le_resolv_list, ¶ms->addr, params->addr_type); + hci_dev_unlock(hdev); + if (exists) return 0; cp.bdaddr_type = params->addr_type;