* [PATCH] Bluetooth: L2CAP: Protect the receive reject-list lookup
@ 2026-10-24 15:29 Cen Zhang
0 siblings, 0 replies; only message in thread
From: Cen Zhang @ 2026-10-24 15:29 UTC (permalink / raw)
To: marcel, luiz.dentz
Cc: linux-bluetooth, linux-kernel, baijiaju1990, jjzuming, zzzccc427
Reject-list entries must remain alive throughout a lookup. Management
updates the list under hdev->lock, but l2cap_recv_frame() traverses it
under conn->lock after l2cap_recv_acldata() has released hdev->lock.
An established LE connection can still receive ACL data after its peer
has been added to the reject list. If a management socket unblocks that
peer during the receive-side lookup, the following ordering is possible:
HCI receive Management
l2cap_recv_acldata()
release hdev->lock
acquire conn->lock
l2cap_recv_frame()
lookup selects entry
unblock_device()
acquire hdev->lock
hci_bdaddr_list_del()
unlink and kfree(entry)
read entry->bdaddr_type
Management does not take conn->lock, so deletion can free the selected
entry before the lookup reads its fields. This causes a use-after-free
read in hci_bdaddr_list_lookup().
Compute the reject decision under the existing device lock in
l2cap_recv_acldata() and pass only the boolean to l2cap_recv_frame().
Take the same protected snapshot before draining pending receive data.
This completes the list access before management can free the entry,
without retaining a list pointer during frame processing.
Taking the device lock inside l2cap_recv_frame() would reverse the
established hdev->lock to conn->lock order. The pending receive worker
uses mutex_trylock() and requeues its existing work item on contention:
l2cap_conn_del() cancels that work synchronously while holding the
device lock, so the worker must not wait for it. Keep the teardown order
and release the device lock before taking conn->lock.
KASAN report as below:
==================================================================
BUG: KASAN: slab-use-after-free in hci_bdaddr_list_lookup+0x145/0x160
Read of size 1 at addr ffff88810cba64d6 by task kworker/u17:0/493
Workqueue: hci0 hci_rx_work
Call Trace:
<TASK>
dump_stack_lvl+0x93/0xd0
print_report+0xce/0x630
? hci_bdaddr_list_lookup+0x145/0x160
? srso_alias_return_thunk+0x5/0xfbef5
? __virt_addr_valid+0x20d/0x410
? hci_bdaddr_list_lookup+0x145/0x160
kasan_report+0xe0/0x110
? hci_bdaddr_list_lookup+0x145/0x160
hci_bdaddr_list_lookup+0x145/0x160
l2cap_recv_frame+0x865/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 604:
kasan_save_stack+0x33/0x60
kasan_save_track+0x14/0x30
__kasan_kmalloc+0xaa/0xb0
__kmalloc_cache_noprof+0x251/0x610
hci_bdaddr_list_add+0xdf/0x2c0
block_device+0x112/0x260
hci_sock_sendmsg+0x128b/0x22e0
sock_write_iter+0x4c0/0x550
do_iter_readv_writev+0x59c/0x860
vfs_writev+0x30e/0xc10
do_writev+0x241/0x2f0
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 606:
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_del+0x1a9/0x2a0
unblock_device+0x112/0x260
hci_sock_sendmsg+0x128b/0x22e0
sock_write_iter+0x4c0/0x550
do_iter_readv_writev+0x59c/0x860
vfs_writev+0x30e/0xc10
do_writev+0x241/0x2f0
do_syscall_64+0x115/0x6a0
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88810cba64c0
which
belongs to the cache kmalloc-32 of size 32
The buggy address is located 22 bytes inside of
freed
32-byte region [ffff88810cba64c0,
ffff88810cba64e0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0
pfn:0x10cba6
flags: 0x200000000000000(node=0|zone=2)
page_type: f5(slab)
raw: 0200000000000000 ffff888100042780 dead000000000100
dead000000000122
raw: 0000000000000000 0000000000400040 00000000f5000000
0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88810cba6380: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc
ffff88810cba6400: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc
>ffff88810cba6480: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc
^
ffff88810cba6500: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc
ffff88810cba6580: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc
==================================================================
Fixes: cc8dba2bc302 ("Bluetooth: Block ATT connection on LE when device is blocked")
Assisted-by: LLM
Signed-off-by: Cen Zhang <zzzccc427@gmail.com>
---
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 83b622a428118..8d0b38f0a0cf8 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -7381,7 +7381,19 @@ static void l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm,
kfree_skb(skb);
}
-static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
+static bool l2cap_is_blocked(struct hci_conn *hcon)
+ __must_hold(&hcon->hdev->lock)
+{
+ /* Since we can't actively block incoming LE connections we must
+ * at least ensure that we ignore incoming data from them.
+ */
+ return hcon->type == LE_LINK &&
+ hci_bdaddr_list_lookup(&hcon->hdev->reject_list, &hcon->dst,
+ bdaddr_dst_type(hcon));
+}
+
+static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb,
+ bool blocked)
__must_hold(&conn->lock)
{
struct l2cap_hdr *lh = (void *) skb->data;
@@ -7408,12 +7420,7 @@ static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
return;
}
- /* Since we can't actively block incoming LE connections we must
- * at least ensure that we ignore incoming data from them.
- */
- if (hcon->type == LE_LINK &&
- hci_bdaddr_list_lookup(&hcon->hdev->reject_list, &hcon->dst,
- bdaddr_dst_type(hcon))) {
+ if (blocked) {
kfree_skb(skb);
return;
}
@@ -7450,10 +7457,22 @@ static void process_pending_rx(struct work_struct *work)
{
struct l2cap_conn *conn = container_of(work, struct l2cap_conn,
pending_rx_work);
+ struct hci_conn *hcon = conn->hcon;
struct sk_buff *skb;
+ bool blocked = false;
BT_DBG("");
+ if (hcon->type == LE_LINK) {
+ /* Teardown cancels this work while holding hdev->lock. */
+ if (!mutex_trylock(&hcon->hdev->lock)) {
+ queue_work(hcon->hdev->workqueue, work);
+ return;
+ }
+ blocked = l2cap_is_blocked(hcon);
+ hci_dev_unlock(hcon->hdev);
+ }
+
mutex_lock(&conn->lock);
/* l2cap_recv_frame() would queue it all again while the encryption
@@ -7461,7 +7480,7 @@ static void process_pending_rx(struct work_struct *work)
*/
while (!test_bit(HCI_CONN_ENC_KEY_SIZE_PEND, conn->hcon->flags) &&
(skb = skb_dequeue(&conn->pending_rx)))
- l2cap_recv_frame(conn, skb);
+ l2cap_recv_frame(conn, skb, blocked);
mutex_unlock(&conn->lock);
}
@@ -8141,6 +8160,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle,
{
struct hci_conn *hcon;
struct l2cap_conn *conn;
+ bool blocked;
int len;
/* Lock hdev for hci_conn, and race on l2cap_data vs. l2cap_conn_del */
@@ -8163,6 +8183,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle,
conn = l2cap_conn_add(hcon);
conn = l2cap_conn_hold_unless_zero(conn);
+ blocked = l2cap_is_blocked(hcon);
hcon = NULL;
hci_dev_unlock(hdev);
@@ -8199,7 +8220,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle,
if (len == skb->len) {
/* Complete frame received */
- l2cap_recv_frame(conn, skb);
+ l2cap_recv_frame(conn, skb, blocked);
goto unlock;
}
@@ -8223,7 +8244,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle,
* setting the socket unreliable.
*/
skb->len = len;
- l2cap_recv_frame(conn, skb);
+ l2cap_recv_frame(conn, skb, blocked);
l2cap_conn_unreliable(conn, ECOMM);
goto unlock;
}
@@ -8273,7 +8294,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle,
*/
struct sk_buff *rx_skb = conn->rx_skb;
conn->rx_skb = NULL;
- l2cap_recv_frame(conn, rx_skb);
+ l2cap_recv_frame(conn, rx_skb, blocked);
}
break;
}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-10-24 15:29 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-24 15:29 [PATCH] Bluetooth: L2CAP: Protect the receive reject-list lookup Cen Zhang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®