* [PATCH 01/16] Bluetooth: L2CAP: take chan->lock for l2cap_chan_add/ready/del
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
@ 2026-08-29 14:19 ` Pauli Virtanen
2026-08-29 14:19 ` [PATCH 02/16] Bluetooth: L2CAP: add l2cap_chan_close_unlocked() and locking helpers Pauli Virtanen
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:19 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
chan->lock must be held for __l2cap_chan_add as eg. calls to
l2cap_chan_close assume chan->conn writes are guarded by it.
It must be held for l2cap_chan_del() due to
l2cap_sock.c:l2cap_chan_conn, l2cap_monitor_timeout, etc.
Similarly it should be held for l2cap_ops::ready (assumed in 6lowpan.c).
Also teardown usually has chan->lock held, it should always have it held
to have the same locking context.
The lock is not correctly held by l2cap_core in several places.
Add the missing locks for l2cap_chan_del/add/ready(), except in
l2cap_ecred_rsp_defer() which needs separate fix as it needs lock
nesting.
Fixes: 6fef032af009 ("Bluetooth: L2CAP: Fix use-after-free in l2cap_sock_new_connection_cb()")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 3 ++-
net/bluetooth/l2cap_core.c | 15 +++++++++++++++
2 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 69d193fee351..43a67562b238 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -973,7 +973,8 @@ int l2cap_chan_check_security(struct l2cap_chan *chan, bool initiator);
void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan);
int l2cap_ertm_init(struct l2cap_chan *chan);
void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
-void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
+void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
+ __must_hold(&chan->lock);
typedef void (*l2cap_chan_func_t)(struct l2cap_chan *chan, void *data);
void l2cap_chan_list(struct l2cap_conn *conn, l2cap_chan_func_t func,
void *data);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 358b11eabd4f..adcf714ec1ed 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -665,7 +665,9 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
{
mutex_lock(&conn->lock);
+ l2cap_chan_lock(chan);
__l2cap_chan_add(conn, chan);
+ l2cap_chan_unlock(chan);
mutex_unlock(&conn->lock);
}
@@ -4065,6 +4067,8 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
if (!chan)
return NULL;
+ l2cap_chan_lock(chan);
+
l2cap_chan_set_defaults(chan, pchan);
chan->ops = pchan->ops;
@@ -4073,10 +4077,13 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
if (pchan->ops->new_connection &&
pchan->ops->new_connection(pchan, chan) < 0) {
l2cap_chan_del(chan, 0);
+ l2cap_chan_unlock(chan);
l2cap_chan_put(chan);
return NULL;
}
+ l2cap_chan_unlock(chan);
+
return chan;
}
@@ -5047,6 +5054,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
goto response_unlock;
}
+ l2cap_chan_lock(chan);
+
bacpy(&chan->src, &conn->hcon->src);
bacpy(&chan->dst, &conn->hcon->dst);
chan->src_type = bdaddr_src_type(conn->hcon);
@@ -5079,6 +5088,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
result = L2CAP_CR_LE_SUCCESS;
}
+ l2cap_chan_unlock(chan);
+
response_unlock:
l2cap_chan_unlock(pchan);
l2cap_chan_put(pchan);
@@ -5271,6 +5282,8 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
continue;
}
+ l2cap_chan_lock(chan);
+
bacpy(&chan->src, &conn->hcon->src);
bacpy(&chan->dst, &conn->hcon->dst);
chan->src_type = bdaddr_src_type(conn->hcon);
@@ -5303,6 +5316,8 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
} else {
l2cap_chan_ready(chan);
}
+
+ l2cap_chan_unlock(chan);
}
unlock:
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 02/16] Bluetooth: L2CAP: add l2cap_chan_close_unlocked() and locking helpers
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
2026-08-29 14:19 ` [PATCH 01/16] Bluetooth: L2CAP: take chan->lock for l2cap_chan_add/ready/del Pauli Virtanen
@ 2026-08-29 14:19 ` Pauli Virtanen
2026-08-29 14:19 ` [PATCH 03/16] Bluetooth: L2CAP: fix race condition in l2cap_sock_shutdown() Pauli Virtanen
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:19 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
l2cap_chan_close() requires holding chan->lock and chan->conn->lock if
associated chan->conn exists, to guard eg. conn->chan_l. Taking the
locks with right ordering requires handling a race condition.
Add helper function l2cap_chan_(un)lock_conn that do the locking right.
Add l2cap_chan_close_unlocked() that does not require locks to be held,
as all callsites do this lock -> close -> unlock pattern.
Link: https://syzkaller.appspot.com/bug?extid=0e4ebcc970728e056324
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 17 ++++++++++
net/bluetooth/l2cap_core.c | 61 ++++++++++++++++++++++++++++++++---
2 files changed, 73 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 43a67562b238..84f557d354ca 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -962,6 +962,8 @@ int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid);
struct l2cap_chan *l2cap_chan_create(void);
void l2cap_chan_close(struct l2cap_chan *chan, int reason);
+void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason)
+ __must_not_hold(&chan->lock);
int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
bdaddr_t *dst, u8 dst_type, u16 timeout);
int l2cap_chan_reconfigure(struct l2cap_chan *chan, __u16 mtu);
@@ -988,4 +990,19 @@ void l2cap_conn_put(struct l2cap_conn *conn);
int l2cap_register_user(struct l2cap_conn *conn, struct l2cap_user *user);
void l2cap_unregister_user(struct l2cap_conn *conn, struct l2cap_user *user);
+bool l2cap_chan_lock_conn(struct l2cap_chan *chan)
+ __acquires(&chan->lock) __cond_acquires(true, &chan->conn->lock);
+
+/* Release macro for l2cap_chan_lock_conn, so context analysis understands it */
+#define l2cap_chan_unlock_conn(chan, conn_locked) \
+ ({ \
+ struct l2cap_chan *__chan = (chan); \
+ struct l2cap_conn *__conn = __chan->conn; \
+ l2cap_chan_unlock(__chan); \
+ if (conn_locked) { \
+ mutex_unlock(&__conn->lock); \
+ l2cap_conn_put(__conn); \
+ } \
+ })
+
#endif /* __L2CAP_H */
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index adcf714ec1ed..58c88e116ddc 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -59,6 +59,7 @@ static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
static void l2cap_retrans_timeout(struct work_struct *work);
static void l2cap_monitor_timeout(struct work_struct *work);
static void l2cap_ack_timeout(struct work_struct *work);
+static void __l2cap_chan_close(struct l2cap_chan *chan, int reason);
static inline u8 bdaddr_type(u8 link_type, u8 bdaddr_type)
{
@@ -422,7 +423,7 @@ static void l2cap_chan_timeout(struct work_struct *work)
else
reason = ETIMEDOUT;
- l2cap_chan_close(chan, reason);
+ __l2cap_chan_close(chan, reason);
chan->ops->close(chan);
@@ -829,7 +830,7 @@ static void l2cap_chan_connect_reject(struct l2cap_chan *chan)
l2cap_send_cmd(conn, chan->ident, L2CAP_CONN_RSP, sizeof(rsp), &rsp);
}
-void l2cap_chan_close(struct l2cap_chan *chan, int reason)
+static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
{
struct l2cap_conn *conn = chan->conn;
@@ -878,8 +879,58 @@ void l2cap_chan_close(struct l2cap_chan *chan, int reason)
break;
}
}
+
+void l2cap_chan_close(struct l2cap_chan *chan, int reason)
+{
+ __l2cap_chan_close(chan, reason);
+}
EXPORT_SYMBOL(l2cap_chan_close);
+/* Take chan->lock. If chan->conn is non-NULL, take new reference on it, take
+ * chan->conn->lock, and return true. Otherwise return false.
+ */
+bool l2cap_chan_lock_conn(struct l2cap_chan *chan)
+ __context_unsafe(/* conditional locking */)
+{
+ /* Handle conn->lock > chan->lock ordering + race on chan->conn */
+ for (;;) {
+ struct l2cap_conn *conn;
+
+ l2cap_chan_lock(chan);
+ conn = chan->conn;
+ if (conn)
+ l2cap_conn_get(conn);
+ l2cap_chan_unlock(chan);
+
+ if (conn)
+ mutex_lock(&conn->lock);
+
+ l2cap_chan_lock(chan);
+
+ if (chan->conn != conn) {
+ l2cap_chan_unlock(chan);
+ if (conn) {
+ mutex_unlock(&conn->lock);
+ l2cap_conn_put(conn);
+ }
+ schedule();
+ continue;
+ }
+
+ return chan->conn;
+ }
+}
+
+void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason)
+{
+ bool have_conn;
+
+ have_conn = l2cap_chan_lock_conn(chan);
+ __l2cap_chan_close(chan, reason);
+ l2cap_chan_unlock_conn(chan, have_conn);
+}
+EXPORT_SYMBOL(l2cap_chan_close_unlocked);
+
static inline u8 l2cap_get_auth_type(struct l2cap_chan *chan)
{
switch (chan->chan_type) {
@@ -1563,7 +1614,7 @@ static void l2cap_conn_start(struct l2cap_conn *conn)
if (!l2cap_mode_supported(chan->mode, conn->feat_mask)
&& test_bit(CONF_STATE2_DEVICE,
&chan->conf_state)) {
- l2cap_chan_close(chan, ECONNRESET);
+ __l2cap_chan_close(chan, ECONNRESET);
l2cap_chan_unlock(chan);
continue;
}
@@ -1571,7 +1622,7 @@ static void l2cap_conn_start(struct l2cap_conn *conn)
if (l2cap_check_enc_key_size(conn->hcon, chan))
l2cap_start_connection(chan);
else
- l2cap_chan_close(chan, ECONNREFUSED);
+ __l2cap_chan_close(chan, ECONNREFUSED);
} else if (chan->state == BT_CONNECT2) {
struct l2cap_conn_rsp rsp;
@@ -7648,7 +7699,7 @@ static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt)
__set_chan_timer(chan, L2CAP_ENC_TIMEOUT);
} else if (chan->sec_level == BT_SECURITY_HIGH ||
chan->sec_level == BT_SECURITY_FIPS)
- l2cap_chan_close(chan, ECONNREFUSED);
+ __l2cap_chan_close(chan, ECONNREFUSED);
} else {
if (chan->sec_level == BT_SECURITY_MEDIUM)
__clear_chan_timer(chan);
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 03/16] Bluetooth: L2CAP: fix race condition in l2cap_sock_shutdown()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
2026-08-29 14:19 ` [PATCH 01/16] Bluetooth: L2CAP: take chan->lock for l2cap_chan_add/ready/del Pauli Virtanen
2026-08-29 14:19 ` [PATCH 02/16] Bluetooth: L2CAP: add l2cap_chan_close_unlocked() and locking helpers Pauli Virtanen
@ 2026-08-29 14:19 ` Pauli Virtanen
2026-08-29 14:19 ` [PATCH 04/16] Bluetooth: 6lowpan: use l2cap_chan_close_unlocked() Pauli Virtanen
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:19 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver,
linux-kernel, Eulgyu Kim, Jaeyoung Chung
l2cap_sock_shutdown() has the race condition
[Task 1] [Task 2]
l2cap_sock_shutdown l2cap_sock_connect
l2cap_chan_lock l2cap_chan_connect
conn = ... /* == NULL*/
l2cap_chan_unlock ------------> l2cap_chan_lock
if (conn) /* false */
__l2cap_chan_add(conn, chan)
l2cap_chan_lock <-------------- l2cap_chan_unlock
l2cap_chan_close /* chan->conn->lock not held! */
conn->lock protects conn->chan_l and is not properly held here.
Use the l2cap_chan_close_unlocked() helper that ensures conn->lock is
held for l2cap_chan_close().
Fixes: ab4eedb790ca ("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del")
Reported-by: Eulgyu Kim <eulgyukim@snu.ac.kr>
Reported-by: Jaeyoung Chung <jjy600901@snu.ac.kr>
Link: https://lore.kernel.org/linux-bluetooth/20260824153908.2327306-1-jjy600901@snu.ac.kr/
Link: https://syzkaller.appspot.com/bug?extid=0e4ebcc970728e056324
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/l2cap_sock.c | 19 +------------------
1 file changed, 1 insertion(+), 18 deletions(-)
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index b553b6356af8..0265b6508682 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1406,7 +1406,6 @@ static int l2cap_sock_shutdown(struct socket *sock, int how)
{
struct sock *sk = sock->sk;
struct l2cap_chan *chan;
- struct l2cap_conn *conn;
int err = 0;
BT_DBG("sock %p, sk %p, how %d", sock, sk, how);
@@ -1463,23 +1462,7 @@ static int l2cap_sock_shutdown(struct socket *sock, int how)
sk->sk_shutdown |= SEND_SHUTDOWN;
release_sock(sk);
- l2cap_chan_lock(chan);
- /* prevent conn structure from being freed */
- conn = l2cap_conn_hold_unless_zero(chan->conn);
- l2cap_chan_unlock(chan);
-
- if (conn)
- /* mutex lock must be taken before l2cap_chan_lock() */
- mutex_lock(&conn->lock);
-
- l2cap_chan_lock(chan);
- l2cap_chan_close(chan, 0);
- l2cap_chan_unlock(chan);
-
- if (conn) {
- mutex_unlock(&conn->lock);
- l2cap_conn_put(conn);
- }
+ l2cap_chan_close_unlocked(chan, 0);
lock_sock(sk);
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 04/16] Bluetooth: 6lowpan: use l2cap_chan_close_unlocked()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (2 preceding siblings ...)
2026-08-29 14:19 ` [PATCH 03/16] Bluetooth: L2CAP: fix race condition in l2cap_sock_shutdown() Pauli Virtanen
@ 2026-08-29 14:19 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 05/16] Bluetooth: L2CAP: remove unused l2cap_chan_close() Pauli Virtanen
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:19 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
6lowpan.c is using l2cap_chan_close() without taking chan->conn->lock,
so it may modify conn->chan_l without holding the guarding lock.
Fix the locking by using the l2cap_chan_close_unlocked() helper that
acquires the necessary locks.
Fixes: 15f32cabf426 ("Bluetooth: 6lowpan: add missing l2cap_chan_lock()")
Link: https://syzkaller.appspot.com/bug?extid=0e4ebcc970728e056324
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/6lowpan.c | 27 +++++++++------------------
1 file changed, 9 insertions(+), 18 deletions(-)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 30f4afa18bc8..4ea55950e599 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -921,9 +921,7 @@ static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
BT_DBG("peer %p chan %p", peer, peer->chan);
- l2cap_chan_lock(peer->chan);
- l2cap_chan_close(peer->chan, ENOENT);
- l2cap_chan_unlock(peer->chan);
+ l2cap_chan_close_unlocked(peer->chan, ENOENT);
return 0;
}
@@ -1025,9 +1023,9 @@ static void disconnect_all_peers(void)
struct lowpan_peer *peer;
int nchans;
- /* l2cap_chan_close() cannot be called from RCU, and lock ordering
- * chan->lock > devices_lock prevents taking write side lock, so copy
- * then close.
+ /* l2cap_chan_close_unlocked() cannot be called from RCU, and lock
+ * ordering chan->lock > devices_lock prevents taking write side lock,
+ * so copy then close.
*/
rcu_read_lock();
@@ -1062,9 +1060,7 @@ static void disconnect_all_peers(void)
spin_unlock(&devices_lock);
for (i = 0; i < nchans; ++i) {
- l2cap_chan_lock(chans[i]);
- l2cap_chan_close(chans[i], ENOENT);
- l2cap_chan_unlock(chans[i]);
+ l2cap_chan_close_unlocked(chans[i], ENOENT);
l2cap_chan_put(chans[i]);
}
} while (nchans);
@@ -1082,9 +1078,7 @@ static void do_enable_set(bool flag)
mutex_lock(&set_lock);
if (listen_chan) {
- l2cap_chan_lock(listen_chan);
- l2cap_chan_close(listen_chan, 0);
- l2cap_chan_unlock(listen_chan);
+ l2cap_chan_close_unlocked(listen_chan, 0);
l2cap_chan_put(listen_chan);
}
@@ -1132,9 +1126,7 @@ static ssize_t lowpan_control_write(struct file *fp,
mutex_lock(&set_lock);
if (listen_chan) {
- l2cap_chan_lock(listen_chan);
- l2cap_chan_close(listen_chan, 0);
- l2cap_chan_unlock(listen_chan);
+ l2cap_chan_close_unlocked(listen_chan, 0);
l2cap_chan_put(listen_chan);
listen_chan = NULL;
}
@@ -1303,10 +1295,9 @@ static void __exit bt_6lowpan_exit(void)
debugfs_remove(lowpan_control_debugfs);
if (listen_chan) {
- l2cap_chan_lock(listen_chan);
- l2cap_chan_close(listen_chan, 0);
- l2cap_chan_unlock(listen_chan);
+ l2cap_chan_close_unlocked(listen_chan, 0);
l2cap_chan_put(listen_chan);
+ listen_chan = NULL;
}
disconnect_devices();
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 05/16] Bluetooth: L2CAP: remove unused l2cap_chan_close()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (3 preceding siblings ...)
2026-08-29 14:19 ` [PATCH 04/16] Bluetooth: 6lowpan: use l2cap_chan_close_unlocked() Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 06/16] Bluetooth: 6lowpan: avoid concurrent peer_del() in bt_6lowpan_disconnect Pauli Virtanen
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
l2cap_chan_close() is now unused, and l2cap_chan_close_unlocked() should
be used instead.
Remove l2cap_chan_close().
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 1 -
net/bluetooth/l2cap_core.c | 6 ------
2 files changed, 7 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 84f557d354ca..e395ab5493f7 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -961,7 +961,6 @@ int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid);
struct l2cap_chan *l2cap_chan_create(void);
-void l2cap_chan_close(struct l2cap_chan *chan, int reason);
void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason)
__must_not_hold(&chan->lock);
int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 58c88e116ddc..c9b3321f7f90 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -880,12 +880,6 @@ static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
}
}
-void l2cap_chan_close(struct l2cap_chan *chan, int reason)
-{
- __l2cap_chan_close(chan, reason);
-}
-EXPORT_SYMBOL(l2cap_chan_close);
-
/* Take chan->lock. If chan->conn is non-NULL, take new reference on it, take
* chan->conn->lock, and return true. Otherwise return false.
*/
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 06/16] Bluetooth: 6lowpan: avoid concurrent peer_del() in bt_6lowpan_disconnect
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (4 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 05/16] Bluetooth: L2CAP: remove unused l2cap_chan_close() Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 07/16] Bluetooth: L2CAP: hold conn->lock for __l2cap_ecred_conn_rsp_defer Pauli Virtanen
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
bt_6lowpan_disconnect() looks up and accesses peer->chan, without
holding locks guaranteeing peer_del() cannot free the peer concurrently.
Take devices_lock to ensure peer can be dereferenced safely.
Fixes: 15f32cabf426 ("Bluetooth: 6lowpan: add missing l2cap_chan_lock()")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/6lowpan.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index 4ea55950e599..ddcdd2aff91f 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -912,16 +912,27 @@ static int bt_6lowpan_connect(bdaddr_t *addr, u8 dst_type)
static int bt_6lowpan_disconnect(struct l2cap_conn *conn, u8 dst_type)
{
struct lowpan_peer *peer;
+ struct l2cap_chan *chan;
BT_DBG("conn %p dst type %u", conn, dst_type);
+ spin_lock(&devices_lock);
+
peer = lookup_peer(conn);
- if (!peer)
+ if (!peer) {
+ spin_unlock(&devices_lock);
return -ENOENT;
+ }
- BT_DBG("peer %p chan %p", peer, peer->chan);
+ chan = peer->chan;
+ l2cap_chan_hold(chan);
- l2cap_chan_close_unlocked(peer->chan, ENOENT);
+ spin_unlock(&devices_lock);
+
+ BT_DBG("peer %p chan %p", peer, chan);
+
+ l2cap_chan_close_unlocked(chan, ENOENT);
+ l2cap_chan_put(chan);
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 07/16] Bluetooth: L2CAP: hold conn->lock for __l2cap_ecred_conn_rsp_defer
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (5 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 06/16] Bluetooth: 6lowpan: avoid concurrent peer_del() in bt_6lowpan_disconnect Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 08/16] Bluetooth: L2CAP: hold l2cap_conn::lock in l2cap_connect_cfm() Pauli Virtanen
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
__l2cap_ecred_conn_rsp_defer() > __l2cap_chan_list_id() accesses
conn->chan_l which is guarded by conn->lock. The lock fails to be held
when calling from l2cap_sock.c.
Fix by using l2cap_chan_conn_lock(), and taking the locks in required
order l2cap_conn::lock > l2cap_chan::lock > sk. Leave fast path with
sk->sk_state precheck. Move the L2CAP defer handling to
l2cap_sock_defer().
The code should also take l2cap_chan_lock() for sibling channels, but
that needs separate fix due to lock nesting.
Fixes: ab4eedb790ca ("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/l2cap_sock.c | 74 ++++++++++++++++++++++++++------------
1 file changed, 51 insertions(+), 23 deletions(-)
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 0265b6508682..dee3025f0ec2 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1243,6 +1243,54 @@ static void l2cap_publish_rx_avail(struct l2cap_chan *chan)
l2cap_chan_rx_avail(chan, -1);
}
+static int l2cap_sock_defer(struct sock *sk)
+{
+ struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+ bool have_conn;
+ int err = 0;
+
+ /* Fast path check */
+ lock_sock(sk);
+ if (sk->sk_state != BT_CONNECT2) {
+ release_sock(sk);
+ return 0;
+ }
+ release_sock(sk);
+
+ have_conn = l2cap_chan_lock_conn(chan);
+ lock_sock(sk);
+
+ if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
+ &bt_sk(sk)->flags)) {
+ err = 1;
+
+ if (!have_conn) {
+ release_sock(sk);
+ err = -ENOTCONN;
+ } else if (chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
+ sk->sk_state = BT_CONNECTED;
+ chan->state = BT_CONNECTED;
+ release_sock(sk);
+ __l2cap_ecred_conn_rsp_defer(chan);
+ } else if (bdaddr_type_is_le(chan->src_type)) {
+ sk->sk_state = BT_CONNECTED;
+ chan->state = BT_CONNECTED;
+ release_sock(sk);
+ __l2cap_le_connect_rsp_defer(chan);
+ } else {
+ sk->sk_state = BT_CONFIG;
+ chan->state = BT_CONFIG;
+ release_sock(sk);
+ __l2cap_connect_rsp_defer(chan);
+ }
+ } else {
+ release_sock(sk);
+ }
+
+ l2cap_chan_unlock_conn(chan, have_conn);
+ return err;
+}
+
static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,
size_t len, int flags)
{
@@ -1254,29 +1302,9 @@ static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg,
return sock_recv_errqueue(sk, msg, len, SOL_BLUETOOTH,
BT_SCM_ERROR);
- lock_sock(sk);
-
- if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP,
- &bt_sk(sk)->flags)) {
- if (pi->chan->mode == L2CAP_MODE_EXT_FLOWCTL) {
- sk->sk_state = BT_CONNECTED;
- pi->chan->state = BT_CONNECTED;
- __l2cap_ecred_conn_rsp_defer(pi->chan);
- } else if (bdaddr_type_is_le(pi->chan->src_type)) {
- sk->sk_state = BT_CONNECTED;
- pi->chan->state = BT_CONNECTED;
- __l2cap_le_connect_rsp_defer(pi->chan);
- } else {
- sk->sk_state = BT_CONFIG;
- pi->chan->state = BT_CONFIG;
- __l2cap_connect_rsp_defer(pi->chan);
- }
-
- err = 0;
- goto done;
- }
-
- release_sock(sk);
+ err = l2cap_sock_defer(sk);
+ if (err)
+ return err < 0 ? err : 0;
if (sock->type == SOCK_STREAM)
err = bt_sock_stream_recvmsg(sock, msg, len, flags);
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 08/16] Bluetooth: L2CAP: hold l2cap_conn::lock in l2cap_connect_cfm()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (6 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 07/16] Bluetooth: L2CAP: hold conn->lock for __l2cap_ecred_conn_rsp_defer Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 09/16] Bluetooth: L2CAP: add annotations for l2cap_chan list locking Pauli Virtanen
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
l2cap_new_connection() -> __l2cap_chan_add() modifies
l2cap_conn::chan_l, which is guarded by l2cap_conn::lock. The lock is
not held in l2cap_connect_cfm().
Fix by holding conn->lock in l2cap_connect_cfm() to make the locking
systematic.
Fixes: ab4eedb790ca ("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del")
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/l2cap_core.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index c9b3321f7f90..44077e3d1e2d 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -7629,6 +7629,8 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
* we left off, because the list lock would prevent calling the
* potentially sleeping l2cap_chan_lock() function.
*/
+ mutex_lock(&conn->lock);
+
pchan = l2cap_global_fixed_chan(NULL, hcon);
while (pchan) {
struct l2cap_chan *chan, *next;
@@ -7653,6 +7655,8 @@ static void l2cap_connect_cfm(struct hci_conn *hcon, u8 status)
pchan = next;
}
+ mutex_unlock(&conn->lock);
+
l2cap_conn_ready(conn);
}
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 09/16] Bluetooth: L2CAP: add annotations for l2cap_chan list locking
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (7 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 08/16] Bluetooth: L2CAP: hold l2cap_conn::lock in l2cap_connect_cfm() Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 10/16] Bluetooth: L2CAP: take lock for l2cap_chan_del in l2cap_ecred_rsp_defer Pauli Virtanen
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
Add context analysis annotations for l2cap_conn::chan_l and chan_list
locking.
Add corresponding required annotations to accessors and callers.
This is not complete chan_l annotation, l2cap_chan::list and
l2cap_chan_del() locking is currently not fully correct, and needs
separate fix + annotations.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 7 ++--
net/bluetooth/l2cap_core.c | 61 ++++++++++++++++++++++++++++++++---
2 files changed, 61 insertions(+), 7 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index e395ab5493f7..a991fc07515c 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -668,7 +668,7 @@ struct l2cap_conn {
struct l2cap_chan *smp;
- struct list_head chan_l;
+ struct list_head chan_l __guarded_by(&lock);
struct mutex lock;
struct kref ref;
struct list_head users;
@@ -954,7 +954,8 @@ void l2cap_cleanup_sockets(void);
bool l2cap_is_socket(struct socket *sock);
void __l2cap_le_connect_rsp_defer(struct l2cap_chan *chan);
-void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan);
+void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
+ __must_hold(&chan->lock) __must_hold(&chan->conn->lock);
void __l2cap_connect_rsp_defer(struct l2cap_chan *chan);
int l2cap_add_psm(struct l2cap_chan *chan, bdaddr_t *src, __le16 psm);
@@ -975,7 +976,7 @@ void l2cap_chan_set_defaults(struct l2cap_chan *chan, struct l2cap_chan *pchan);
int l2cap_ertm_init(struct l2cap_chan *chan);
void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan);
void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
- __must_hold(&chan->lock);
+ __must_hold(&conn->lock) __must_hold(&chan->lock);
typedef void (*l2cap_chan_func_t)(struct l2cap_chan *chan, void *data);
void l2cap_chan_list(struct l2cap_conn *conn, l2cap_chan_func_t func,
void *data);
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 44077e3d1e2d..ce51b0b0b37d 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -44,8 +44,8 @@ bool enable_ecred = IS_ENABLED(CONFIG_BT_LE_L2CAP_ECRED);
static u32 l2cap_feat_mask = L2CAP_FEAT_FIXED_CHAN | L2CAP_FEAT_UCD;
-static LIST_HEAD(chan_list);
static DEFINE_RWLOCK(chan_list_lock);
+static __guarded_by(&chan_list_lock) LIST_HEAD(chan_list);
static struct sk_buff *l2cap_build_cmd(struct l2cap_conn *conn,
u8 code, u8 ident, u16 dlen, void *data);
@@ -87,6 +87,7 @@ static inline u8 bdaddr_dst_type(struct hci_conn *hcon)
static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn,
u16 cid)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *c;
@@ -99,6 +100,7 @@ static struct l2cap_chan *__l2cap_get_chan_by_dcid(struct l2cap_conn *conn,
static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn,
u16 cid)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *c;
@@ -114,6 +116,7 @@ static struct l2cap_chan *__l2cap_get_chan_by_scid(struct l2cap_conn *conn,
*/
static struct l2cap_chan *l2cap_get_chan_by_scid(struct l2cap_conn *conn,
u16 cid)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *c;
@@ -129,6 +132,7 @@ static struct l2cap_chan *l2cap_get_chan_by_scid(struct l2cap_conn *conn,
*/
static struct l2cap_chan *l2cap_get_chan_by_dcid(struct l2cap_conn *conn,
u16 cid)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *c;
@@ -141,6 +145,7 @@ static struct l2cap_chan *l2cap_get_chan_by_dcid(struct l2cap_conn *conn,
static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn,
u8 ident)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *c;
@@ -153,6 +158,7 @@ static struct l2cap_chan *__l2cap_get_chan_by_ident(struct l2cap_conn *conn,
static struct l2cap_chan *__l2cap_global_chan_by_addr(__le16 psm, bdaddr_t *src,
u8 src_type)
+ __must_hold_shared(&chan_list_lock)
{
struct l2cap_chan *c;
@@ -230,6 +236,7 @@ int l2cap_add_scid(struct l2cap_chan *chan, __u16 scid)
}
static u16 l2cap_alloc_cid(struct l2cap_conn *conn)
+ __must_hold(&conn->lock)
{
u16 cid, dyn_end;
@@ -728,6 +735,7 @@ EXPORT_SYMBOL_GPL(l2cap_chan_del);
static void __l2cap_chan_list_id(struct l2cap_conn *conn, u16 id,
l2cap_chan_func_t func, void *data)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan, *l;
@@ -739,6 +747,7 @@ static void __l2cap_chan_list_id(struct l2cap_conn *conn, u16 id,
static void __l2cap_chan_list(struct l2cap_conn *conn, l2cap_chan_func_t func,
void *data)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan;
@@ -806,6 +815,9 @@ static void l2cap_chan_ecred_connect_reject(struct l2cap_chan *chan)
{
l2cap_state_change(chan, BT_DISCONN);
+ lockdep_assert_held(&chan->lock);
+ lockdep_assert_held(&chan->conn->lock);
+
__l2cap_ecred_conn_rsp_defer(chan);
}
@@ -1416,6 +1428,7 @@ static void l2cap_ecred_defer_connect(struct l2cap_chan *chan, void *data)
}
static void l2cap_ecred_connect(struct l2cap_chan *chan)
+ __must_hold(&chan->conn->lock)
{
struct l2cap_conn *conn = chan->conn;
struct l2cap_ecred_conn_data data;
@@ -1449,6 +1462,7 @@ static void l2cap_ecred_connect(struct l2cap_chan *chan)
}
static void l2cap_le_start(struct l2cap_chan *chan)
+ __must_hold(&chan->conn->lock)
{
struct l2cap_conn *conn = chan->conn;
@@ -1469,6 +1483,7 @@ static void l2cap_le_start(struct l2cap_chan *chan)
}
static void l2cap_start_connection(struct l2cap_chan *chan)
+ __must_hold(&chan->conn->lock)
{
if (chan->conn->hcon->type == LE_LINK) {
l2cap_le_start(chan);
@@ -1518,6 +1533,7 @@ static bool l2cap_check_enc_key_size(struct hci_conn *hcon,
}
static void l2cap_do_start(struct l2cap_chan *chan)
+ __must_hold(&chan->conn->lock)
{
struct l2cap_conn *conn = chan->conn;
@@ -1584,6 +1600,7 @@ static void l2cap_send_disconn_req(struct l2cap_chan *chan, int err)
/* ---- L2CAP connections ---- */
static void l2cap_conn_start(struct l2cap_conn *conn)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan, *tmp;
@@ -1592,6 +1609,8 @@ static void l2cap_conn_start(struct l2cap_conn *conn)
list_for_each_entry_safe(chan, tmp, &conn->chan_l, list) {
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
l2cap_chan_ready(chan);
l2cap_chan_unlock(chan);
@@ -1708,6 +1727,8 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
if (hcon->type == LE_LINK) {
l2cap_le_start(chan);
} else if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED) {
@@ -1730,6 +1751,7 @@ static void l2cap_conn_ready(struct l2cap_conn *conn)
/* Notify sockets that we cannot guaranty reliability anymore */
static void l2cap_conn_unreliable(struct l2cap_conn *conn, int err)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan;
@@ -3027,6 +3049,7 @@ static void l2cap_pass_to_tx_fbit(struct l2cap_chan *chan,
/* Copy frame to all raw sockets on that connection */
static void l2cap_raw_recv(struct l2cap_conn *conn, struct sk_buff *skb)
+ __must_hold(&conn->lock)
{
struct sk_buff *nskb;
struct l2cap_chan *chan;
@@ -4073,6 +4096,7 @@ static void l2cap_conf_rfc_get(struct l2cap_chan *chan, void *rsp, int len)
static inline int l2cap_command_rej(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data;
@@ -4105,6 +4129,7 @@ static inline int l2cap_command_rej(struct l2cap_conn *conn,
*/
static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
struct l2cap_chan *pchan)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan;
@@ -4134,6 +4159,7 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
u8 *data, u8 rsp_code)
+ __must_hold(&conn->lock)
__context_unsafe(/* conditional locking */)
{
struct l2cap_conn_req *req = (struct l2cap_conn_req *) data;
@@ -4264,6 +4290,7 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
static int l2cap_connect_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len, u8 *data)
+ __must_hold(&conn->lock)
{
if (cmd_len < sizeof(struct l2cap_conn_req))
return -EPROTO;
@@ -4275,6 +4302,7 @@ static int l2cap_connect_req(struct l2cap_conn *conn,
static int l2cap_connect_create_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_conn_rsp *rsp = (struct l2cap_conn_rsp *) data;
u16 scid, dcid, result, status;
@@ -4392,6 +4420,7 @@ static void cmd_reject_invalid_cid(struct l2cap_conn *conn, u8 ident,
static inline int l2cap_config_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_conf_req *req = (struct l2cap_conf_req *) data;
u16 dcid, flags;
@@ -4505,6 +4534,7 @@ static inline int l2cap_config_req(struct l2cap_conn *conn,
static inline int l2cap_config_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_conf_rsp *rsp = (struct l2cap_conf_rsp *)data;
u16 scid, flags, result;
@@ -4614,6 +4644,7 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn,
static inline int l2cap_disconnect_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_disconn_req *req = (struct l2cap_disconn_req *) data;
struct l2cap_disconn_rsp rsp;
@@ -4655,6 +4686,7 @@ static inline int l2cap_disconnect_req(struct l2cap_conn *conn,
static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_disconn_rsp *rsp = (struct l2cap_disconn_rsp *) data;
u16 dcid, scid;
@@ -4742,6 +4774,7 @@ static inline int l2cap_information_req(struct l2cap_conn *conn,
static inline int l2cap_information_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_info_rsp *rsp = (struct l2cap_info_rsp *) data;
u16 type, result;
@@ -4849,6 +4882,7 @@ static inline int l2cap_conn_param_update_req(struct l2cap_conn *conn,
static int l2cap_le_connect_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_le_conn_rsp *rsp = (struct l2cap_le_conn_rsp *) data;
struct hci_conn *hcon = conn->hcon;
@@ -4955,6 +4989,7 @@ static void l2cap_put_ident(struct l2cap_conn *conn, u8 code, u8 id)
static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
int err = 0;
@@ -5016,6 +5051,7 @@ static inline int l2cap_bredr_sig_cmd(struct l2cap_conn *conn,
static int l2cap_le_connect_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_le_conn_req *req = (struct l2cap_le_conn_req *) data;
struct l2cap_le_conn_rsp rsp;
@@ -5163,6 +5199,7 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
static inline int l2cap_le_credits(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_le_credits *pkt;
struct l2cap_chan *chan;
@@ -5212,6 +5249,7 @@ static inline int l2cap_le_credits(struct l2cap_conn *conn,
static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_ecred_conn_req *req = (void *) data;
DEFINE_RAW_FLEX(struct l2cap_ecred_conn_rsp, pdu, dcid, L2CAP_ECRED_MAX_CID);
@@ -5384,6 +5422,7 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_ecred_conn_rsp *rsp = (void *) data;
struct hci_conn *hcon = conn->hcon;
@@ -5511,6 +5550,7 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_ecred_reconf_req *req = (void *) data;
struct l2cap_ecred_reconf_rsp rsp;
@@ -5609,6 +5649,7 @@ static inline int l2cap_ecred_reconf_req(struct l2cap_conn *conn,
static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan, *tmp;
struct l2cap_ecred_reconf_rsp *rsp = (void *)data;
@@ -5649,6 +5690,7 @@ static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
struct l2cap_cmd_rej_unk *rej = (struct l2cap_cmd_rej_unk *) data;
struct l2cap_chan *chan;
@@ -5676,6 +5718,7 @@ static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
struct l2cap_cmd_hdr *cmd, u16 cmd_len,
u8 *data)
+ __must_hold(&conn->lock)
{
int err = 0;
@@ -5740,6 +5783,7 @@ static inline int l2cap_le_sig_cmd(struct l2cap_conn *conn,
static inline void l2cap_le_sig_channel(struct l2cap_conn *conn,
struct sk_buff *skb)
+ __must_hold(&conn->lock)
{
struct hci_conn *hcon = conn->hcon;
struct l2cap_cmd_hdr *cmd;
@@ -5798,6 +5842,7 @@ static inline void l2cap_sig_send_mtu_rej(struct l2cap_conn *conn, u8 ident)
static inline void l2cap_sig_channel(struct l2cap_conn *conn,
struct sk_buff *skb)
+ __must_hold(&conn->lock)
{
struct hci_conn *hcon = conn->hcon;
struct l2cap_cmd_hdr *cmd;
@@ -7035,6 +7080,7 @@ static int l2cap_ecred_data_rcv(struct l2cap_chan *chan, struct sk_buff *skb)
static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
struct sk_buff *skb)
+ __must_hold(&conn->lock)
{
struct l2cap_chan *chan;
@@ -7143,6 +7189,7 @@ static void l2cap_conless_channel(struct l2cap_conn *conn, __le16 psm,
}
static void l2cap_recv_frame(struct l2cap_conn *conn, struct sk_buff *skb)
+ __must_hold(&conn->lock)
{
struct l2cap_hdr *lh = (void *) skb->data;
struct hci_conn *hcon = conn->hcon;
@@ -7252,9 +7299,9 @@ static struct l2cap_conn *l2cap_conn_add(struct hci_conn *hcon)
hci_dev_test_flag(hcon->hdev, HCI_FORCE_BREDR_SMP)))
conn->local_fixed_chan |= L2CAP_FC_SMP_BREDR;
- mutex_init(&conn->lock);
-
- INIT_LIST_HEAD(&conn->chan_l);
+ scoped_guard(mutex_init, &conn->lock) {
+ INIT_LIST_HEAD(&conn->chan_l);
+ }
INIT_LIST_HEAD(&conn->users);
INIT_DELAYED_WORK(&conn->info_timer, l2cap_info_timeout);
@@ -7466,6 +7513,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
__l2cap_chan_add(conn, chan);
+ lockdep_assert_held(&chan->conn->lock);
+
/* l2cap_chan_add takes its own ref so we can drop this one */
hci_conn_drop(hcon);
@@ -7688,6 +7737,8 @@ static void l2cap_disconn_cfm(struct hci_conn *hcon, u8 reason)
}
static inline void l2cap_check_encryption(struct l2cap_chan *chan, u8 encrypt)
+ __must_hold(&chan->lock)
+ __must_hold(&chan->conn->lock)
{
if (chan->chan_type != L2CAP_CHAN_CONN_ORIENTED)
return;
@@ -7720,6 +7771,8 @@ static void l2cap_security_cfm(struct hci_conn *hcon, u8 status, u8 encrypt)
list_for_each_entry(chan, &conn->chan_l, list) {
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
BT_DBG("chan %p scid 0x%4.4x state %s", chan, chan->scid,
state_to_string(chan->state));
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 10/16] Bluetooth: L2CAP: take lock for l2cap_chan_del in l2cap_ecred_rsp_defer
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (8 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 09/16] Bluetooth: L2CAP: add annotations for l2cap_chan list locking Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 11/16] Bluetooth: L2CAP: hold chan in l2cap_ecred_conn_rsp() Pauli Virtanen
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
l2cap_ecred_rsp_defer() calls l2cap_chan_del without holding chan->lock,
which ends up calling ops->teardown() with wrong lock context.
Fix by taking chan->lock in l2cap_ecred_rsp_defer(). AB-BA deadlocks
between sibling l2cap_chan are avoided here via requiring l2cap_conn::lock
to serialize all nested l2cap_chan locking on same nesting level.
In current code, there is no nested l2cap_chan locking on same nesting
level, so we can add this new requirement.
Also return early from __l2cap_ecred_conn_rsp_defer() if chan did not
have FLAG_DEFER_SETUP, as then no RSP shall be sent for it, to make sure
SMP channels are excluded.
Also hold chan reference over l2cap_chan_del(), in case chan_l reference
was the last.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 4 +++
net/bluetooth/l2cap_core.c | 60 +++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index a991fc07515c..efd59b6f8afd 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -758,6 +758,10 @@ enum {
* otherwise considers all channels equal and will e.g. complain about a
* connection oriented channel triggering SMP procedures or a listening
* channel creating and locking a child channel.
+ *
+ * Lock nesting of channels at the same nesting level is allowed if the channels
+ * have the same l2cap_chan::conn and l2cap_chan::conn.lock is taken before the
+ * nested locks. l2cap_chan_try_sibling_lock() must be used.
*/
enum {
L2CAP_NESTING_SMP,
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index ce51b0b0b37d..750b13f76203 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -848,6 +848,8 @@ static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
+ lockdep_assert_held(&chan->lock);
+
switch (chan->state) {
case BT_LISTEN:
chan->ops->teardown(chan, 0);
@@ -3943,6 +3945,7 @@ static void l2cap_ecred_list_defer(struct l2cap_chan *chan, void *data)
}
struct l2cap_ecred_rsp_data {
+ struct l2cap_chan *locked_chan;
struct {
struct l2cap_ecred_conn_rsp_hdr rsp;
__le16 scid[L2CAP_ECRED_MAX_CID];
@@ -3950,11 +3953,42 @@ struct l2cap_ecred_rsp_data {
int count;
};
+/* Lock @chan if it is not @locked_chan, and has same or lower nesting level.
+ *
+ * They must have the same chan->conn, and conn->lock must be held.
+ *
+ * Caller must ensure @chan has lock nesting level <= that of @locked_chan, as
+ * nested locking of l2cap_chan of different levels is allowed also without
+ * holding conn->lock.
+ *
+ * See l2cap.h for the global l2cap_chan locking rules.
+ */
+static bool l2cap_chan_try_sibling_lock(struct l2cap_chan *chan,
+ struct l2cap_chan *locked_chan)
+ __must_hold(&locked_chan->lock)
+ __must_hold(&locked_chan->conn->lock)
+ __cond_acquires(true, &chan->lock)
+{
+ if (chan == locked_chan)
+ return false;
+
+ if (WARN_ON_ONCE(locked_chan->conn != chan->conn))
+ return false;
+
+ if (WARN_ON_ONCE(atomic_read(&locked_chan->nesting)
+ < atomic_read(&chan->nesting)))
+ return false;
+
+ mutex_lock_nest_lock(&chan->lock, &locked_chan->conn->lock);
+ return true;
+}
+
static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data)
{
struct l2cap_ecred_rsp_data *rsp = data;
struct l2cap_ecred_conn_rsp *rsp_flex =
container_of(&rsp->pdu.rsp, struct l2cap_ecred_conn_rsp, hdr);
+ bool locked;
/* Check if channel for outgoing connection or if it wasn't deferred
* since in those cases it must be skipped.
@@ -3963,6 +3997,22 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data)
!test_and_clear_bit(FLAG_DEFER_SETUP, &chan->flags))
return;
+ lockdep_assert_held(&rsp->locked_chan->lock);
+ lockdep_assert_held(&rsp->locked_chan->conn->lock);
+
+ l2cap_chan_hold(chan);
+
+ locked = l2cap_chan_try_sibling_lock(chan, rsp->locked_chan);
+
+ /* Cannot occur: PARENT channels do not appear in chan_l, and SMP
+ * channels never have FLAG_DEFER_SETUP.
+ */
+ if (context_unsafe(!locked && chan != rsp->locked_chan))
+ goto done;
+
+ lockdep_assert_held(&chan->lock);
+ lockdep_assert_held(&chan->conn->lock);
+
/* Reset ident so only one response is sent */
chan->ident = 0;
@@ -3971,6 +4021,12 @@ static void l2cap_ecred_rsp_defer(struct l2cap_chan *chan, void *data)
rsp_flex->dcid[rsp->count++] = cpu_to_le16(chan->scid);
else
l2cap_chan_del(chan, ECONNRESET);
+
+done:
+ if (locked)
+ l2cap_chan_unlock(chan);
+
+ l2cap_chan_put(chan);
}
void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
@@ -3982,11 +4038,15 @@ void __l2cap_ecred_conn_rsp_defer(struct l2cap_chan *chan)
if (!id)
return;
+ if (!test_bit(FLAG_DEFER_SETUP, &chan->flags))
+ return;
BT_DBG("chan %p id %d", chan, id);
memset(&data, 0, sizeof(data));
+ data.locked_chan = chan;
+
data.pdu.rsp.mtu = cpu_to_le16(chan->imtu);
data.pdu.rsp.mps = cpu_to_le16(chan->mps);
data.pdu.rsp.credits = cpu_to_le16(chan->rx_credits);
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 11/16] Bluetooth: L2CAP: hold chan in l2cap_ecred_conn_rsp()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (9 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 10/16] Bluetooth: L2CAP: take lock for l2cap_chan_del in l2cap_ecred_rsp_defer Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 12/16] Bluetooth: L2CAP: annotate locking for l2cap_chan_del() Pauli Virtanen
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
l2cap_chan_del() calls l2cap_chan_put() to drop the conn->chan_l
reference. If this was the last reference, UAF follows.
l2cap_ecred_conn_rsp() iterates chan_l list and calls l2cap_chan_del()
on some members, without holding chan reference.
Fix by holding refcount while using chan after l2cap_chan_del().
Since orig is looked up by dcid provided by remote, it's also possible
orig == chan, so reference needs to be held also after orig use.
Fixes: 41c2713b204e ("Bluetooth: L2CAP: Fix possible crash on l2cap_ecred_conn_rsp")
Assisted-by: deepseek-v4-flash
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/l2cap_core.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 750b13f76203..cc86399ef0f5 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -5513,12 +5513,14 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
chan->state == BT_CONNECTED)
continue;
+ l2cap_chan_hold(chan);
l2cap_chan_lock(chan);
/* Check that there is a dcid for each pending channel */
if (cmd_len < sizeof(dcid)) {
l2cap_chan_del(chan, ECONNREFUSED);
l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
continue;
}
@@ -5557,6 +5559,8 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
__set_chan_timer(orig, 0);
l2cap_chan_unlock(orig);
}
+
+ l2cap_chan_put(chan);
continue;
}
@@ -5602,6 +5606,7 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
}
l2cap_chan_unlock(chan);
+ l2cap_chan_put(chan);
}
return err;
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 12/16] Bluetooth: L2CAP: annotate locking for l2cap_chan_del()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (10 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 11/16] Bluetooth: L2CAP: hold chan in l2cap_ecred_conn_rsp() Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 13/16] Bluetooth: L2CAP: annotate locking for l2cap_ops callbacks Pauli Virtanen
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
Add context analysis annotations for chan->lock and chan->conn->lock
involving l2cap_chan_del() usage.
Add necessary annotations and related lockdep_assert_held to callers.
Move struct l2cap_ops definition after struct l2cap_conn, so that the
callbacks can be annotated.
In l2cap_chan_close_unlocked() we consider chan->conn->lock as locked
even if chan->conn == NULL, to avoid needing to define separate
__l2cap_chan_close/del for this NULL case.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 56 +++++++++++++++++++----------------
net/bluetooth/6lowpan.c | 2 ++
net/bluetooth/l2cap_core.c | 49 +++++++++++++++++++++++++-----
3 files changed, 74 insertions(+), 33 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index efd59b6f8afd..f612c9f884d1 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -614,31 +614,6 @@ struct l2cap_chan {
struct mutex lock;
};
-struct l2cap_ops {
- char *name;
-
- int (*new_connection)(struct l2cap_chan *chan,
- struct l2cap_chan *new_chan);
- int (*recv) (struct l2cap_chan * chan,
- struct sk_buff *skb);
- void (*teardown) (struct l2cap_chan *chan, int err);
- void (*close) (struct l2cap_chan *chan);
- void (*state_change) (struct l2cap_chan *chan,
- int state, int err);
- void (*ready) (struct l2cap_chan *chan);
- void (*defer) (struct l2cap_chan *chan);
- void (*resume) (struct l2cap_chan *chan);
- void (*suspend) (struct l2cap_chan *chan);
- void (*set_shutdown) (struct l2cap_chan *chan);
- long (*get_sndtimeo) (struct l2cap_chan *chan);
- struct pid *(*get_peer_pid) (struct l2cap_chan *chan);
- struct sk_buff *(*alloc_skb) (struct l2cap_chan *chan,
- unsigned long hdr_len,
- unsigned long len, int nb);
- int (*filter) (struct l2cap_chan * chan,
- struct sk_buff *skb);
-};
-
struct l2cap_conn {
struct hci_conn *hcon;
struct hci_chan *hchan;
@@ -674,6 +649,34 @@ struct l2cap_conn {
struct list_head users;
};
+struct l2cap_ops {
+ char *name;
+
+ int (*new_connection)(struct l2cap_chan *chan,
+ struct l2cap_chan *new_chan);
+ int (*recv) (struct l2cap_chan * chan,
+ struct sk_buff *skb);
+ void (*teardown) (struct l2cap_chan *chan, int err)
+ __must_hold(&chan->lock);
+ void (*close) (struct l2cap_chan *chan);
+ void (*state_change) (struct l2cap_chan *chan,
+ int state, int err);
+ void (*ready) (struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
+ __must_hold(&chan->conn->lock);
+ void (*defer) (struct l2cap_chan *chan);
+ void (*resume) (struct l2cap_chan *chan);
+ void (*suspend) (struct l2cap_chan *chan);
+ void (*set_shutdown) (struct l2cap_chan *chan);
+ long (*get_sndtimeo) (struct l2cap_chan *chan);
+ struct pid *(*get_peer_pid) (struct l2cap_chan *chan);
+ struct sk_buff *(*alloc_skb) (struct l2cap_chan *chan,
+ unsigned long hdr_len,
+ unsigned long len, int nb);
+ int (*filter) (struct l2cap_chan * chan,
+ struct sk_buff *skb);
+};
+
struct l2cap_user {
struct list_head list;
int (*probe) (struct l2cap_conn *conn, struct l2cap_user *user);
@@ -984,7 +987,8 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
typedef void (*l2cap_chan_func_t)(struct l2cap_chan *chan, void *data);
void l2cap_chan_list(struct l2cap_conn *conn, l2cap_chan_func_t func,
void *data);
-void l2cap_chan_del(struct l2cap_chan *chan, int err);
+void l2cap_chan_del(struct l2cap_chan *chan, int err)
+ __must_hold(&chan->lock) __must_hold(&chan->conn->lock);
void l2cap_send_conn_req(struct l2cap_chan *chan);
struct l2cap_conn *l2cap_conn_get(struct l2cap_conn *conn);
diff --git a/net/bluetooth/6lowpan.c b/net/bluetooth/6lowpan.c
index ddcdd2aff91f..836add41f5d1 100644
--- a/net/bluetooth/6lowpan.c
+++ b/net/bluetooth/6lowpan.c
@@ -722,6 +722,8 @@ static int setup_netdev(struct l2cap_chan *chan, struct lowpan_btle_dev **dev)
}
static inline void chan_ready_cb(struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
+ __must_hold(&chan->conn->lock)
{
struct lowpan_btle_dev *dev;
bool new_netdev = false;
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index cc86399ef0f5..88a9596e4801 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -59,7 +59,8 @@ static void l2cap_tx(struct l2cap_chan *chan, struct l2cap_ctrl *control,
static void l2cap_retrans_timeout(struct work_struct *work);
static void l2cap_monitor_timeout(struct work_struct *work);
static void l2cap_ack_timeout(struct work_struct *work);
-static void __l2cap_chan_close(struct l2cap_chan *chan, int reason);
+static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
+ __must_hold(&chan->lock) __must_hold(&chan->conn->lock);
static inline u8 bdaddr_type(u8 link_type, u8 bdaddr_type)
{
@@ -681,6 +682,8 @@ void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
void l2cap_chan_del(struct l2cap_chan *chan, int err)
{
+ lockdep_assert(!chan->conn || lockdep_is_held(&chan->conn->lock));
+
__clear_chan_timer(chan);
BT_DBG("chan %p, err %d, state %s", chan, err,
@@ -812,12 +815,11 @@ static void l2cap_chan_le_connect_reject(struct l2cap_chan *chan)
}
static void l2cap_chan_ecred_connect_reject(struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
+ __must_hold(&chan->conn->lock)
{
l2cap_state_change(chan, BT_DISCONN);
- lockdep_assert_held(&chan->lock);
- lockdep_assert_held(&chan->conn->lock);
-
__l2cap_ecred_conn_rsp_defer(chan);
}
@@ -848,8 +850,6 @@ static void __l2cap_chan_close(struct l2cap_chan *chan, int reason)
BT_DBG("chan %p state %s", chan, state_to_string(chan->state));
- lockdep_assert_held(&chan->lock);
-
switch (chan->state) {
case BT_LISTEN:
chan->ops->teardown(chan, 0);
@@ -934,7 +934,10 @@ void l2cap_chan_close_unlocked(struct l2cap_chan *chan, int reason)
bool have_conn;
have_conn = l2cap_chan_lock_conn(chan);
- __l2cap_chan_close(chan, reason);
+
+ /* Context analysis: consider chan->conn->lock held also if conn NULL */
+ context_unsafe(__l2cap_chan_close(chan, reason));
+
l2cap_chan_unlock_conn(chan, have_conn);
}
EXPORT_SYMBOL(l2cap_chan_close_unlocked);
@@ -1336,6 +1339,8 @@ void l2cap_send_conn_req(struct l2cap_chan *chan)
}
static void l2cap_chan_ready(struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
+ __must_hold(&chan->conn->lock)
{
/* The channel may have already been flagged as connected in
* case of receiving data before the L2CAP info req/rsp
@@ -1464,6 +1469,7 @@ static void l2cap_ecred_connect(struct l2cap_chan *chan)
}
static void l2cap_le_start(struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
__must_hold(&chan->conn->lock)
{
struct l2cap_conn *conn = chan->conn;
@@ -1485,6 +1491,7 @@ static void l2cap_le_start(struct l2cap_chan *chan)
}
static void l2cap_start_connection(struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
__must_hold(&chan->conn->lock)
{
if (chan->conn->hcon->type == LE_LINK) {
@@ -1535,6 +1542,7 @@ static bool l2cap_check_enc_key_size(struct hci_conn *hcon,
}
static void l2cap_do_start(struct l2cap_chan *chan)
+ __must_hold(&chan->lock)
__must_hold(&chan->conn->lock)
{
struct l2cap_conn *conn = chan->conn;
@@ -1886,6 +1894,8 @@ static void l2cap_conn_del(struct hci_conn *hcon, int err)
l2cap_chan_hold(chan);
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
l2cap_chan_del(chan, err);
chan->ops->close(chan);
@@ -4204,6 +4214,8 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
__l2cap_chan_add(conn, chan);
+ lockdep_assert_held(&chan->conn->lock);
+
if (pchan->ops->new_connection &&
pchan->ops->new_connection(pchan, chan) < 0) {
l2cap_chan_del(chan, 0);
@@ -4403,6 +4415,8 @@ static int l2cap_connect_create_rsp(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
switch (result) {
case L2CAP_CR_SUCCESS:
if (__l2cap_get_chan_by_dcid(conn, dcid)) {
@@ -4504,6 +4518,8 @@ static inline int l2cap_config_req(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
if (chan->state != BT_CONFIG && chan->state != BT_CONNECT2 &&
chan->state != BT_CONNECTED) {
cmd_reject_invalid_cid(conn, cmd->ident, chan->scid,
@@ -4618,6 +4634,8 @@ static inline int l2cap_config_rsp(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
switch (result) {
case L2CAP_CONF_SUCCESS:
l2cap_conf_rfc_get(chan, rsp->data, len);
@@ -4727,6 +4745,8 @@ static inline int l2cap_disconnect_req(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
rsp.dcid = cpu_to_le16(chan->scid);
rsp.scid = cpu_to_le16(chan->dcid);
l2cap_send_cmd(conn, cmd->ident, L2CAP_DISCONN_RSP, sizeof(rsp), &rsp);
@@ -4767,6 +4787,8 @@ static inline int l2cap_disconnect_rsp(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
if (chan->state != BT_DISCONN) {
l2cap_chan_unlock(chan);
l2cap_chan_put(chan);
@@ -4979,6 +5001,8 @@ static int l2cap_le_connect_rsp(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
switch (result) {
case L2CAP_CR_LE_SUCCESS:
if (__l2cap_get_chan_by_dcid(conn, dcid)) {
@@ -5197,6 +5221,8 @@ static int l2cap_le_connect_req(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
bacpy(&chan->src, &conn->hcon->src);
bacpy(&chan->dst, &conn->hcon->dst);
chan->src_type = bdaddr_src_type(conn->hcon);
@@ -5427,6 +5453,8 @@ static inline int l2cap_ecred_conn_req(struct l2cap_conn *conn,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
bacpy(&chan->src, &conn->hcon->src);
bacpy(&chan->dst, &conn->hcon->dst);
chan->src_type = bdaddr_src_type(conn->hcon);
@@ -5516,6 +5544,8 @@ static inline int l2cap_ecred_conn_rsp(struct l2cap_conn *conn,
l2cap_chan_hold(chan);
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
/* Check that there is a dcid for each pending channel */
if (cmd_len < sizeof(dcid)) {
l2cap_chan_del(chan, ECONNREFUSED);
@@ -5743,6 +5773,8 @@ static inline int l2cap_ecred_reconf_rsp(struct l2cap_conn *conn,
continue;
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
l2cap_chan_del(chan, ECONNRESET);
l2cap_chan_unlock(chan);
@@ -5772,6 +5804,7 @@ static inline int l2cap_le_command_rej(struct l2cap_conn *conn,
goto done;
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
l2cap_chan_del(chan, ECONNREFUSED);
l2cap_chan_unlock(chan);
l2cap_chan_put(chan);
@@ -7159,6 +7192,8 @@ static void l2cap_data_channel(struct l2cap_conn *conn, u16 cid,
l2cap_chan_lock(chan);
+ lockdep_assert_held(&chan->conn->lock);
+
BT_DBG("chan %p, len %d", chan, skb->len);
/* If we receive data on a fixed channel before the info req/rsp
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 13/16] Bluetooth: L2CAP: annotate locking for l2cap_ops callbacks
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (11 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 12/16] Bluetooth: L2CAP: annotate locking for l2cap_chan_del() Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 14/16] Bluetooth: L2CAP: make concurrent l2cap_set_timer() refcounting safe Pauli Virtanen
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
Annotate current locking context for l2cap_ops callbacks.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 13 +++++++++----
net/bluetooth/l2cap_core.c | 1 +
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index f612c9f884d1..3d00ffb66cc9 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -653,21 +653,26 @@ struct l2cap_ops {
char *name;
int (*new_connection)(struct l2cap_chan *chan,
- struct l2cap_chan *new_chan);
+ struct l2cap_chan *new_chan)
+ __must_hold(&chan->lock)
+ __must_hold(&new_chan->lock);
int (*recv) (struct l2cap_chan * chan,
struct sk_buff *skb);
void (*teardown) (struct l2cap_chan *chan, int err)
__must_hold(&chan->lock);
- void (*close) (struct l2cap_chan *chan);
+ void (*close) (struct l2cap_chan *chan)
+ __must_hold(&chan->lock);
void (*state_change) (struct l2cap_chan *chan,
int state, int err);
void (*ready) (struct l2cap_chan *chan)
__must_hold(&chan->lock)
__must_hold(&chan->conn->lock);
void (*defer) (struct l2cap_chan *chan);
- void (*resume) (struct l2cap_chan *chan);
+ void (*resume) (struct l2cap_chan *chan)
+ __must_hold(&chan->lock);
void (*suspend) (struct l2cap_chan *chan);
- void (*set_shutdown) (struct l2cap_chan *chan);
+ void (*set_shutdown) (struct l2cap_chan *chan)
+ __must_hold(&chan->lock);
long (*get_sndtimeo) (struct l2cap_chan *chan);
struct pid *(*get_peer_pid) (struct l2cap_chan *chan);
struct sk_buff *(*alloc_skb) (struct l2cap_chan *chan,
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index 88a9596e4801..ff9529f2eb15 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -4200,6 +4200,7 @@ static inline int l2cap_command_rej(struct l2cap_conn *conn,
static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
struct l2cap_chan *pchan)
__must_hold(&conn->lock)
+ __must_hold(&pchan->lock)
{
struct l2cap_chan *chan;
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 14/16] Bluetooth: L2CAP: make concurrent l2cap_set_timer() refcounting safe
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (12 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 13/16] Bluetooth: L2CAP: annotate locking for l2cap_ops callbacks Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 15/16] Bluetooth: L2CAP: remove conditional locking from l2cap_connect() Pauli Virtanen
2026-08-29 14:20 ` [PATCH 16/16] Bluetooth: L2CAP: refuse __l2cap_chan_add if chan already has conn Pauli Virtanen
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
Since l2cap_set_timer() does not check return value of
schedule_delayed_work(), two concurrent calls may result to l2cap_chan
refcount leak.
Change the refcounting by using mod_delayed_work() and checking its
return value.
Code paths aside from l2cap_chan_busy() hold chan->lock, so this has
little correctness impact.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
include/net/bluetooth/l2cap.h | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h
index 3d00ffb66cc9..efb9b7f422d1 100644
--- a/include/net/bluetooth/l2cap.h
+++ b/include/net/bluetooth/l2cap.h
@@ -859,12 +859,11 @@ static inline void l2cap_set_timer(struct l2cap_chan *chan,
BT_DBG("chan %p state %s timeout %ld", chan,
state_to_string(chan->state), timeout);
- /* If delayed work cancelled do not hold(chan)
- since it is already done with previous set_timer */
- if (!cancel_delayed_work(work))
- l2cap_chan_hold(chan);
+ l2cap_chan_hold(chan);
- schedule_delayed_work(work, timeout);
+ /* put(chan) if timer was already queued so it already has a ref */
+ if (mod_delayed_work(system_percpu_wq, work, timeout))
+ l2cap_chan_put(chan);
}
static inline bool l2cap_clear_timer(struct l2cap_chan *chan,
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 15/16] Bluetooth: L2CAP: remove conditional locking from l2cap_connect()
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (13 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 14/16] Bluetooth: L2CAP: make concurrent l2cap_set_timer() refcounting safe Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
2026-08-29 14:20 ` [PATCH 16/16] Bluetooth: L2CAP: refuse __l2cap_chan_add if chan already has conn Pauli Virtanen
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
Context analysis does not understand conditional locking.
Restructure l2cap_connect() by removing conditional locking at the cost
of some code duplication, so that static analysis can see its content.
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/l2cap_core.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index ff9529f2eb15..a4299bc10c2f 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -4233,7 +4233,6 @@ static struct l2cap_chan *l2cap_new_connection(struct l2cap_conn *conn,
static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
u8 *data, u8 rsp_code)
__must_hold(&conn->lock)
- __context_unsafe(/* conditional locking */)
{
struct l2cap_conn_req *req = (struct l2cap_conn_req *) data;
struct l2cap_conn_rsp rsp;
@@ -4250,7 +4249,13 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
&conn->hcon->dst, ACL_LINK);
if (!pchan) {
result = L2CAP_CR_BAD_PSM;
- goto response;
+
+ rsp.scid = cpu_to_le16(scid);
+ rsp.dcid = cpu_to_le16(dcid);
+ rsp.result = cpu_to_le16(result);
+ rsp.status = cpu_to_le16(status);
+ l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp);
+ return;
}
l2cap_chan_lock(pchan);
@@ -4332,9 +4337,6 @@ static void l2cap_connect(struct l2cap_conn *conn, struct l2cap_cmd_hdr *cmd,
rsp.status = cpu_to_le16(status);
l2cap_send_cmd(conn, cmd->ident, rsp_code, sizeof(rsp), &rsp);
- if (!pchan)
- return;
-
if (result == L2CAP_CR_PEND && status == L2CAP_CS_NO_INFO) {
struct l2cap_info_req info;
info.type = cpu_to_le16(L2CAP_IT_FEAT_MASK);
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread* [PATCH 16/16] Bluetooth: L2CAP: refuse __l2cap_chan_add if chan already has conn
2026-08-29 14:19 [PATCH 00/16] Bluetooth: L2CAP: fix and annotate l2cap_conn::chan_l locking Pauli Virtanen
` (14 preceding siblings ...)
2026-08-29 14:20 ` [PATCH 15/16] Bluetooth: L2CAP: remove conditional locking from l2cap_connect() Pauli Virtanen
@ 2026-08-29 14:20 ` Pauli Virtanen
15 siblings, 0 replies; 17+ messages in thread
From: Pauli Virtanen @ 2026-08-29 14:20 UTC (permalink / raw)
To: linux-bluetooth
Cc: Pauli Virtanen, marcel, luiz.dentz, oss, error27, elver, linux-kernel
l2cap_chan may be linked to l2cap_conn at most once. This is assumed in
several places, eg l2cap_chan_del cleanup.
There is a TOCTOU race where the invariant is violated:
[Task 1] [Task 2]
l2cap_chan_connect l2cap_sock_bind
l2cap_chan_lock lock_sock
l2cap_state_change if (sk->sk_state != BT_OPEN)
chan->state = BT_CONNECT
l2cap_sock_state_change_cb chan->state = BT_BOUND
sk->sk_state = BT_BOUND
lock_sock <------------------ release_sock
sk->sk_state = BT_CONNECT
l2cap_sock_connect() does not check sk->sk_state, so since chan->state
is now BT_BOUND, subsequent connect() ends up with second
__l2cap_chan_add.
Explicitly document and check the invariant in __l2cap_chan_add with
WARN_ON_ONCE. The only callsite where it could be hit is
l2cap_chan_connect, so add pre-check there to avoid relying on
chan->state. chan->state read/write is not properly guarded currently so
there can be other TOCTOUC problems.
Add l2cap_lock_chan in l2cap_sock_bind() to guard chan->state write.
Fixes: b66774b48dd9 ("Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref")
Assisted-by: deepseek-4-flash # finding the race condition
Signed-off-by: Pauli Virtanen <pav@iki.fi>
---
net/bluetooth/l2cap_core.c | 7 ++++++-
net/bluetooth/l2cap_sock.c | 2 ++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c
index a4299bc10c2f..a86c154d4a9a 100644
--- a/net/bluetooth/l2cap_core.c
+++ b/net/bluetooth/l2cap_core.c
@@ -623,6 +623,10 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan)
BT_DBG("conn %p, psm 0x%2.2x, dcid 0x%4.4x", conn,
__le16_to_cpu(chan->psm), chan->dcid);
+ /* Caller must ensure l2cap_chan is linked to l2cap_conn only once */
+ if (WARN_ON_ONCE(chan->conn || test_bit(FLAG_DEL, &chan->flags)))
+ return;
+
conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM;
chan->conn = l2cap_conn_get(conn);
@@ -7604,7 +7608,8 @@ int l2cap_chan_connect(struct l2cap_chan *chan, __le16 psm, u16 cid,
mutex_lock(&conn->lock);
l2cap_chan_lock(chan);
- if (cid && __l2cap_get_chan_by_dcid(conn, cid)) {
+ if ((cid && __l2cap_get_chan_by_dcid(conn, cid)) || chan->conn ||
+ test_bit(FLAG_DEL, &chan->flags)) {
hci_conn_drop(hcon);
err = -EBUSY;
goto chan_unlock;
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index dee3025f0ec2..278adb05c4c9 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -109,6 +109,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, i
return -EINVAL;
}
+ l2cap_chan_lock(chan);
lock_sock(sk);
if (sk->sk_state != BT_OPEN) {
@@ -174,6 +175,7 @@ static int l2cap_sock_bind(struct socket *sock, struct sockaddr_unsized *addr, i
done:
release_sock(sk);
+ l2cap_chan_unlock(chan);
return err;
}
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread