From: Siwei Zhang <oss@fourdim.xyz>
To: Marcel Holtmann <marcel@holtmann.org>,
Luiz Augusto von Dentz <luiz.dentz@gmail.com>
Cc: linux-bluetooth@vger.kernel.org,
"Safa Karakuş" <safa.karakus@secunnix.com>,
"Siwei Zhang" <oss@fourdim.xyz>
Subject: [PATCH v7 RESEND 1/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen()
Date: Wed, 20 May 2026 12:38:17 -0400 [thread overview]
Message-ID: <20260520163859.2859782-2-oss@fourdim.xyz> (raw)
In-Reply-To: <20260520163859.2859782-1-oss@fourdim.xyz>
l2cap_sock_cleanup_listen() calls l2cap_chan_close() without holding
conn->lock. A concurrent task iterating conn->chan_l under conn->lock
can access a channel that has been removed from the list and freed.
That can result in a slab-use-after-free.
Split cleanup into two phases. Drain the accept queue under the parent's
sk_lock onto a local list, taking a sock reference on each child so it
survives the lock drop. Then release the parent and close every drained
child under conn->lock + chan_lock, using
l2cap_chan_hold_unless_zero()/l2cap_conn_hold_unless_zero() to cope with
a teardown that has already started, and skipping any chan whose
->data has been cleared. Reacquire the parent's sk_lock at the end so
the caller's contract is preserved.
Noted that commit ab4eedb790ca
("Bluetooth: L2CAP: Fix corrupted list in hci_chan_del")
renamed chan_lock to lock in l2cap_conn.
Fixes: 3df91ea20e74 ("Bluetooth: Revert to mutexes from RCU list")
Cc: stable@kernel.org
Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Siwei Zhang <oss@fourdim.xyz>
---
net/bluetooth/l2cap_sock.c | 57 ++++++++++++++++++++++++++++++++------
1 file changed, 49 insertions(+), 8 deletions(-)
diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c
index 71e8c1b45bce..48b018991ced 100644
--- a/net/bluetooth/l2cap_sock.c
+++ b/net/bluetooth/l2cap_sock.c
@@ -1471,27 +1471,68 @@ static int l2cap_sock_release(struct socket *sock)
static void l2cap_sock_cleanup_listen(struct sock *parent)
{
struct sock *sk;
+ struct bt_sock *bs, *tmp;
+ LIST_HEAD(killable);
BT_DBG("parent %p state %s", parent,
state_to_string(parent->sk_state));
- /* Close not yet accepted channels */
+ /* Drain unaccepted children to a local list, pinning each so it
+ * survives the parent lock-drop below.
+ */
while ((sk = bt_accept_dequeue(parent, NULL))) {
- struct l2cap_chan *chan = l2cap_pi(sk)->chan;
+ sock_hold(sk);
+ list_add_tail(&bt_sk(sk)->accept_q, &killable);
+ }
- BT_DBG("child chan %p state %s", chan,
- state_to_string(chan->state));
+ /* l2cap_chan_close() must run under conn->lock, but the rx path
+ * (l2cap_sock_new_connection_cb) takes conn->lock then lock_sock(parent),
+ * so parent must be released before we close. Draining the queue
+ * first means a concurrent cleanup_listen() on the same parent finds
+ * it empty and is a no-op.
+ */
+ release_sock(parent);
+
+ list_for_each_entry_safe(bs, tmp, &killable, accept_q) {
+ struct l2cap_chan *chan;
+ struct l2cap_conn *conn;
+
+ sk = (struct sock *)bs;
+ list_del_init(&bs->accept_q);
+
+ chan = l2cap_chan_hold_unless_zero(l2cap_pi(sk)->chan);
+ if (!chan) {
+ sock_put(sk);
+ continue;
+ }
- l2cap_chan_hold(chan);
+ l2cap_chan_lock(chan);
+ conn = l2cap_conn_hold_unless_zero(chan->conn);
+ l2cap_chan_unlock(chan);
+
+ if (conn)
+ mutex_lock(&conn->lock);
l2cap_chan_lock(chan);
- __clear_chan_timer(chan);
- l2cap_chan_close(chan, ECONNRESET);
- l2cap_sock_kill(sk);
+ BT_DBG("child chan %p state %s", chan,
+ state_to_string(chan->state));
+
+ if (chan->data) {
+ __clear_chan_timer(chan);
+ l2cap_chan_close(chan, ECONNRESET);
+ l2cap_sock_kill(sk);
+ }
l2cap_chan_unlock(chan);
+ if (conn) {
+ mutex_unlock(&conn->lock);
+ l2cap_conn_put(conn);
+ }
l2cap_chan_put(chan);
+ sock_put(sk);
}
+
+ lock_sock_nested(parent, L2CAP_NESTING_PARENT);
}
static struct l2cap_chan *l2cap_sock_new_connection_cb(struct l2cap_chan *chan)
--
2.54.0
next prev parent reply other threads:[~2026-05-20 16:39 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-20 16:38 [PATCH v7 RESEND 0/1] Bluetooth: L2CAP: Fix slab-use-after-free in l2cap_sock_cleanup_listen() Siwei Zhang
2026-05-20 16:38 ` Siwei Zhang [this message]
2026-05-20 18:08 ` bluez.test.bot
2026-05-20 18:26 ` [PATCH v7 RESEND 0/1] " Luiz Augusto von Dentz
2026-05-20 18:56 ` Siwei Zhang
2026-05-20 19:40 ` Luiz Augusto von Dentz
2026-05-20 20:08 ` Siwei Zhang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260520163859.2859782-2-oss@fourdim.xyz \
--to=oss@fourdim.xyz \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=safa.karakus@secunnix.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox