From 780267e8a232dd8cfb57e3e7212edb1af8e4d14d Mon Sep 17 00:00:00 2001 From: Tedd Ho-Jeong An Date: Wed, 4 Nov 2020 21:09:48 -0800 Subject: [PATCH 01/21] workflow: Add workflow files for ci This patch adds workflow files for ci: [sync.yml] - The workflow file for scheduled work - Sync the repo with upstream repo and rebase the workflow branch - Review the patches in the patchwork and creates the PR if needed [ci.yml] - The workflow file for CI tasks - Run CI tests when PR is created Signed-off-by: Tedd Ho-Jeong An --- .github/workflows/ci.yml | 25 ++++++++++++++++++++++ .github/workflows/sync.yml | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/sync.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000..3a2c45c37553c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: [pull_request] + +jobs: + ci: + runs-on: ubuntu-latest + name: CI for Pull Request + steps: + - name: Checkout the source code + uses: actions/checkout@v3 + with: + path: src/src + + - name: CI + uses: tedd-an/bzcafe@main + with: + task: ci + base_folder: src + space: kernel + github_token: ${{ secrets.GITHUB_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + patchwork_user: ${{ secrets.PATCHWORK_USER }} + diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml new file mode 100644 index 0000000000000..3883d55a23267 --- /dev/null +++ b/.github/workflows/sync.yml @@ -0,0 +1,43 @@ +name: Sync + +on: + schedule: + - cron: "*/30 * * * *" + +jobs: + sync_repo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + ref: master + + - name: Sync Repo + uses: tedd-an/bzcafe@main + with: + task: sync + upstream_repo: 'https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git' + github_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Cleanup PR + uses: tedd-an/bzcafe@main + with: + task: cleanup + github_token: ${{ secrets.ACTION_TOKEN }} + + sync_patchwork: + needs: sync_repo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Sync Patchwork + uses: tedd-an/bzcafe@main + with: + task: patchwork + space: kernel + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + patchwork_user: ${{ secrets.PATCHWORK_USER }} + From 0665eae540562ea439331d47fc930b996dbeceb8 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 9 Apr 2026 12:57:28 -0400 Subject: [PATCH 02/21] workflows: Make use bluez/action-ci action This replaces the bzcafe action with bluez/action-ci so we can maintain everything in the github bluez organization Signed-off-by: Luiz Augusto von Dentz --- .github/workflows/ci.yml | 2 +- .github/workflows/sync.yml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a2c45c37553c..58bd27096c3cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: path: src/src - name: CI - uses: tedd-an/bzcafe@main + uses: bluez/action-ci@main with: task: ci base_folder: src diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 3883d55a23267..6fb956309caa0 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -13,14 +13,14 @@ jobs: ref: master - name: Sync Repo - uses: tedd-an/bzcafe@main + uses: bluez/action-ci@main with: task: sync upstream_repo: 'https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git' github_token: ${{ secrets.GITHUB_TOKEN }} - name: Cleanup PR - uses: tedd-an/bzcafe@main + uses: bluez/action-ci@main with: task: cleanup github_token: ${{ secrets.ACTION_TOKEN }} @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v3 - name: Sync Patchwork - uses: tedd-an/bzcafe@main + uses: bluez/action-ci@main with: task: patchwork space: kernel @@ -40,4 +40,3 @@ jobs: email_token: ${{ secrets.EMAIL_TOKEN }} patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} patchwork_user: ${{ secrets.PATCHWORK_USER }} - From 131b04f2cfb6f8d55c60c5b7ff9d0436b07b54ee Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 9 Apr 2026 13:18:58 -0400 Subject: [PATCH 03/21] workflow/sync: Attempt to sync every 5 minutes This attempts to sync every 5 minutes instead of 30. Signed-off-by: Luiz Augusto von Dentz --- .github/workflows/sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 6fb956309caa0..92164bde28cef 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -2,7 +2,7 @@ name: Sync on: schedule: - - cron: "*/30 * * * *" + - cron: "*/5 * * * *" jobs: sync_repo: From 68ea8ecd3b1a603b83241872fc11bfa7576e0834 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Thu, 9 Apr 2026 15:21:22 -0400 Subject: [PATCH 04/21] workflow/sync: Set workflow to use worflow branch bluez/action-ci uses master as default branch for workflow which is incorrect for kernel Signed-off-by: Luiz Augusto von Dentz --- .github/workflows/sync.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 92164bde28cef..5e95af92ab1ab 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -16,6 +16,7 @@ jobs: uses: bluez/action-ci@main with: task: sync + workflow: workflow upstream_repo: 'https://git.kernel.org/pub/scm/linux/kernel/git/bluetooth/bluetooth-next.git' github_token: ${{ secrets.GITHUB_TOKEN }} @@ -35,6 +36,7 @@ jobs: uses: bluez/action-ci@main with: task: patchwork + workflow: workflow space: kernel github_token: ${{ secrets.ACTION_TOKEN }} email_token: ${{ secrets.EMAIL_TOKEN }} From 616126a266b4ab44266f6c74678b647c0048794d Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Mon, 13 Apr 2026 16:41:33 -0400 Subject: [PATCH 05/21] workflow/ci: Add checks:write permission and explicit reopened trigger The CI action now creates individual GitHub Check Runs per test, which requires 'checks: write' permission on the GITHUB_TOKEN. Also make the pull_request trigger types explicit to include 'reopened', allowing CI to be retriggered by closing and reopening a PR. --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58bd27096c3cf..60d318f805ff1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,12 @@ name: CI -on: [pull_request] +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: + checks: write + pull-requests: write jobs: ci: From 5ecbd933d7451eda6f3fd5743daa5dc2bde2e3bc Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:19:56 +0300 Subject: [PATCH 06/21] Bluetooth: L2CAP: take chan->lock for l2cap_chan_add/ready/del 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 --- 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 69d193fee351a..43a67562b2387 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 358b11eabd4f5..adcf714ec1edd 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: From dfd5936268aa868cb8c685bc7ccb67753d32205f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:19:57 +0300 Subject: [PATCH 07/21] Bluetooth: L2CAP: add l2cap_chan_close_unlocked() and locking helpers 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 --- 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 43a67562b2387..84f557d354cae 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 adcf714ec1edd..58c88e116ddcf 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); From 14678218ae00e9415f6bbda26fd1fead3ea2999e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:19:58 +0300 Subject: [PATCH 08/21] Bluetooth: L2CAP: fix race condition in l2cap_sock_shutdown() 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 Reported-by: Jaeyoung Chung 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 --- 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 b553b6356af81..0265b65086821 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); From e9ca93e83c4c9fe008a38e57899894610c1ddd4f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:19:59 +0300 Subject: [PATCH 09/21] Bluetooth: 6lowpan: use l2cap_chan_close_unlocked() 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 --- 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 30f4afa18bc8f..4ea55950e599f 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(); From bff182b388c46cf9824da1a655bd85fc1268d666 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:00 +0300 Subject: [PATCH 10/21] Bluetooth: L2CAP: remove unused l2cap_chan_close() l2cap_chan_close() is now unused, and l2cap_chan_close_unlocked() should be used instead. Remove l2cap_chan_close(). Signed-off-by: Pauli Virtanen --- 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 84f557d354cae..e395ab5493f70 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 58c88e116ddcf..c9b3321f7f902 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. */ From 487bff8293280b5636ae9a1b1c7f8a832b1573f2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:01 +0300 Subject: [PATCH 11/21] Bluetooth: 6lowpan: avoid concurrent peer_del() in bt_6lowpan_disconnect 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 --- 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 4ea55950e599f..ddcdd2aff91fe 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; + } + + chan = peer->chan; + l2cap_chan_hold(chan); + + spin_unlock(&devices_lock); - BT_DBG("peer %p chan %p", peer, peer->chan); + BT_DBG("peer %p chan %p", peer, chan); - l2cap_chan_close_unlocked(peer->chan, ENOENT); + l2cap_chan_close_unlocked(chan, ENOENT); + l2cap_chan_put(chan); return 0; } From 413cb3876415c3f7f5cb56156c47da0f9d72884a Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:02 +0300 Subject: [PATCH 12/21] Bluetooth: L2CAP: hold conn->lock for __l2cap_ecred_conn_rsp_defer __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 --- net/bluetooth/l2cap_sock.c | 68 +++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 0265b65086821..dee3025f0ec24 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1243,40 +1243,68 @@ static void l2cap_publish_rx_avail(struct l2cap_chan *chan) l2cap_chan_rx_avail(chan, -1); } -static int l2cap_sock_recvmsg(struct socket *sock, struct msghdr *msg, - size_t len, int flags) +static int l2cap_sock_defer(struct sock *sk) { - struct sock *sk = sock->sk; - struct l2cap_pinfo *pi = l2cap_pi(sk); - int err; + struct l2cap_chan *chan = l2cap_pi(sk)->chan; + bool have_conn; + int err = 0; - if (unlikely(flags & MSG_ERRQUEUE)) - return sock_recv_errqueue(sk, msg, len, SOL_BLUETOOTH, - BT_SCM_ERROR); + /* 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)) { - if (pi->chan->mode == L2CAP_MODE_EXT_FLOWCTL) { + err = 1; + + if (!have_conn) { + release_sock(sk); + err = -ENOTCONN; + } else if (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)) { + 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; - pi->chan->state = BT_CONNECTED; - __l2cap_le_connect_rsp_defer(pi->chan); + chan->state = BT_CONNECTED; + release_sock(sk); + __l2cap_le_connect_rsp_defer(chan); } else { sk->sk_state = BT_CONFIG; - pi->chan->state = BT_CONFIG; - __l2cap_connect_rsp_defer(pi->chan); + chan->state = BT_CONFIG; + release_sock(sk); + __l2cap_connect_rsp_defer(chan); } - - err = 0; - goto done; + } else { + release_sock(sk); } - 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) +{ + struct sock *sk = sock->sk; + struct l2cap_pinfo *pi = l2cap_pi(sk); + int err; + + if (unlikely(flags & MSG_ERRQUEUE)) + return sock_recv_errqueue(sk, msg, len, SOL_BLUETOOTH, + BT_SCM_ERROR); + + 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); From f1bd36e73a90f46a4390651e18f20eb514790759 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:03 +0300 Subject: [PATCH 13/21] Bluetooth: L2CAP: hold l2cap_conn::lock in l2cap_connect_cfm() 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 --- 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 c9b3321f7f902..44077e3d1e2d6 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); } From 48be065df30a6bbeb8d8b38f64f959fc179b7d29 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:04 +0300 Subject: [PATCH 14/21] Bluetooth: L2CAP: add annotations for l2cap_chan list locking 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 --- 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 e395ab5493f70..a991fc07515cd 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 44077e3d1e2d6..ce51b0b0b37dc 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)); From 3a338cadad5041d9781a96d911a9b2c965d28435 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:05 +0300 Subject: [PATCH 15/21] Bluetooth: L2CAP: take lock for l2cap_chan_del in l2cap_ecred_rsp_defer 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 --- 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 a991fc07515cd..efd59b6f8afdd 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 ce51b0b0b37dc..750b13f76203a 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); From c8ee09d942336e071fe2cc0cf16c0a25e2ca1f2f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:06 +0300 Subject: [PATCH 16/21] Bluetooth: L2CAP: hold chan in l2cap_ecred_conn_rsp() 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 --- 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 750b13f76203a..cc86399ef0f52 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; From 2c853d4d8b761e8c5f768b50d2598f0c43b25afb Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:07 +0300 Subject: [PATCH 17/21] Bluetooth: L2CAP: annotate locking for l2cap_chan_del() 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 --- 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 efd59b6f8afdd..f612c9f884d1a 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 ddcdd2aff91fe..836add41f5d16 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 cc86399ef0f52..88a9596e48018 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 From 6edadbd38749009b9dba599b858dae34701cddfe Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:08 +0300 Subject: [PATCH 18/21] Bluetooth: L2CAP: annotate locking for l2cap_ops callbacks Annotate current locking context for l2cap_ops callbacks. Signed-off-by: Pauli Virtanen --- 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 f612c9f884d1a..3d00ffb66cc98 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 88a9596e48018..ff9529f2eb156 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; From 34507b530aa5ca8fbf8418613b1aac39c5794af2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:09 +0300 Subject: [PATCH 19/21] Bluetooth: L2CAP: make concurrent l2cap_set_timer() refcounting safe 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 --- 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 3d00ffb66cc98..efb9b7f422d1d 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, From 985804cb4f54a9805d0a49587b3407ae65998ae0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:10 +0300 Subject: [PATCH 20/21] Bluetooth: L2CAP: remove conditional locking from l2cap_connect() 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 --- 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 ff9529f2eb156..a4299bc10c2f2 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); From 3f686ce937cfe4306fabb223cb6e6dd5eed97060 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 29 Aug 2026 17:20:11 +0300 Subject: [PATCH 21/21] Bluetooth: L2CAP: refuse __l2cap_chan_add if chan already has conn 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 --- 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 a4299bc10c2f2..a86c154d4a9a8 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 dee3025f0ec24..278adb05c4c90 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; }