summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorCen Zhang <zzzccc427@gmail.com>2026-10-08 12:26:26 +0800
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>2026-10-14 14:38:25 -0400
commit4020751fd0df45ce5029a5dd69e1bb68a25cb4dd (patch)
tree33add281c2050e58eb790c5a2602b4d91399d645 /net
parent645ee6b176c958579cff1c783413d5cff65c8197 (diff)
Bluetooth: L2CAP: Hold the listener while notifying child teardown
A pending child's listener must remain alive until the child's teardown callback finishes notifying it. l2cap_sock_teardown_cb() saves the raw bt_sk(sk)->parent pointer, calls bt_accept_unlink(), then invokes parent->sk_data_ready(). The accept queue holds a reference to the child, but the callback takes no reference to the listener across unlink. When an incoming child has not been accepted, HCI disconnection can reach l2cap_conn_del() and the child's teardown callback while another task closes the listener. The listener channel has no connection, so the child's connection and channel locks do not serialize listener release. The following ordering is possible: HCI child teardown Listener release lock the child socket save parent bt_accept_unlink(sk) drain the empty accept queue bt_sock_unlink() shut down the listener channel sock_orphan() l2cap_sock_kill() parent->sk_data_ready(parent) If no other reference delays freeing the listener, the final notification reads sk_data_ready through freed socket storage. The child lock cannot stop listener release once the child is no longer on the accept queue. Take a listener socket reference before unlinking the child and release it after sk_data_ready() returns. While the child remains queued, its socket lock prevents listener cleanup from completing the dequeue, so the listener is still alive when the reference is acquired. The reference then protects the notification after unlink without changing lock order or the ordering of queue removal and wakeup. KASAN report as below: ================================================================== BUG: KASAN: slab-use-after-free in l2cap_sock_teardown_cb+0x467/0x490 Read of size 8 at addr ffff88810b99b188 by task kworker/u17:3/516 Workqueue: hci1 hci_rx_work Call Trace: <TASK> dump_stack_lvl+0x93/0xd0 print_report+0xce/0x630 ? l2cap_sock_teardown_cb+0x467/0x490 ? srso_alias_return_thunk+0x5/0xfbef5 ? __virt_addr_valid+0x20d/0x410 ? l2cap_sock_teardown_cb+0x467/0x490 kasan_report+0xe0/0x110 ? l2cap_sock_teardown_cb+0x467/0x490 l2cap_sock_teardown_cb+0x467/0x490 l2cap_chan_del+0x123/0x8f0 l2cap_conn_del+0x31b/0x740 ? hci_cmd_sync_submit+0x264/0x320 ? __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 502: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 __kasan_kmalloc+0xaa/0xb0 __kmalloc_noprof+0x2d7/0x770 sk_prot_alloc+0x13e/0x260 sk_alloc+0x37/0xb40 bt_sock_alloc+0x40/0x3b0 l2cap_sock_create+0x116/0x310 bt_sock_create+0x171/0x330 __sock_create+0x2c4/0x730 __sys_socket+0x141/0x210 __x64_sys_socket+0x77/0xc0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f Freed by task 502: kasan_save_stack+0x33/0x60 kasan_save_track+0x14/0x30 kasan_save_free_info+0x3b/0x60 __kasan_slab_free+0x5f/0x80 kfree+0x307/0x580 __sk_destruct+0x64f/0x790 sk_destruct+0xb3/0xd0 __sk_free+0xdd/0x370 sk_free+0x51/0x80 l2cap_sock_kill+0x17a/0x2a0 l2cap_sock_release+0x1d0/0x310 __sock_release+0xb8/0x270 sock_close+0x21/0x30 __fput+0x39f/0xa60 fput_close_sync+0xff/0x200 __x64_sys_close+0x8c/0xf0 do_syscall_64+0x115/0x6a0 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff88810b99b000 which belongs to the cache kmalloc-2k of size 2048 The buggy address is located 392 bytes inside of freed 2048-byte region [ffff88810b99b000, ffff88810b99b800) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x10b998 head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 flags: 0x200000000000040(head|node=0|zone=2) page_type: f5(slab) raw: 0200000000000040 ffff888100042f00 dead000000000100 dead000000000122 raw: 0000000000000000 0000000000080008 00000000f5000000 0000000000000000 head: 0200000000000040 ffff888100042f00 dead000000000100 dead000000000122 head: 0000000000000000 0000000000080008 00000000f5000000 0000000000000000 head: 0200000000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff head: 0000000000000000 0000000000000000 00000000ffffffff 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffff88810b99b080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff88810b99b100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb >ffff88810b99b180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ^ ffff88810b99b200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ffff88810b99b280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb ================================================================== Assisted-by: LLM Signed-off-by: Cen Zhang <zzzccc427@gmail.com> Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Diffstat (limited to 'net')
-rw-r--r--net/bluetooth/l2cap_sock.c6
1 files changed, 6 insertions, 0 deletions
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index c92f31b997dd..65097d6ef9d8 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1751,8 +1751,14 @@ static void l2cap_sock_teardown_cb(struct l2cap_chan *chan, int err)
sk->sk_err = err;
if (parent) {
+ /*
+ * Keep the listener alive after unlinking the
+ * child.
+ */
+ sock_hold(parent);
bt_accept_unlink(sk);
parent->sk_data_ready(parent);
+ sock_put(parent);
} else {
sk->sk_state_change(sk);
}