From: Baul Lee <baul.lee@xbow.com>
To: linux-bluetooth@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: luiz.dentz@gmail.com, marcel@holtmann.org,
federico.kirschbaum@xbow.com, Baul Lee <baul.lee@xbow.com>,
stable@vger.kernel.org
Subject: [PATCH] Bluetooth: SCO: fix sco_conn double free on outgoing connect
Date: Sun, 26 Jul 2026 14:54:31 +0900 [thread overview]
Message-ID: <20260726055431.42350-1-baul.lee@xbow.com> (raw)
sco_conn is refcounted with a kref that sco_conn_add() initialises to 1.
That single reference is the connection's association reference, owned
by the hcon and released when the link goes down.
The incoming attach path takes an extra reference for the socket before
__sco_chan_add(), but the outgoing path, sco_connect() -> sco_chan_add(),
does not. An outgoing SCO socket is therefore attached while conn->ref
is still 1, and two unrelated teardown paths both release that one
reference: sco_chan_del(), reached from close(), and the !sk branch of
sco_conn_del(), reached from the sco_connect_cfm() / sco_disconn_cfm()
callbacks.
When an outgoing SCO setup fails while the socket is being closed, the
two race, starting from conn->ref == 1:
1. sco_chan_del() clears conn->sk.
2. sco_conn_del() takes a temporary reference (ref 2) and, seeing
conn->sk already cleared, gets a NULL sk.
3. sco_chan_del() puts the reference it believes it owns (ref 1).
4. sco_conn_del() drops its temporary reference (ref 0) and the
sco_conn is freed.
5. sco_conn_del() takes the !sk branch and puts the freed object.
KASAN reports a slab-use-after-free of the kmalloc-128 sco_conn in
sco_chan_del(), followed by a refcount_t underflow. Both operations are
reachable from an unprivileged AF_BLUETOOTH / BTPROTO_SCO socket doing
connect() and close().
Give the socket its own reference on the outgoing attach so that the two
teardown owners no longer contend for a single reference, and drop the
association reference in sco_conn_del()'s socket-kill path so that it is
released exactly once there as well, symmetrically with the existing !sk
branch. sco_conn_ready() consequently has to take both references
itself, because sco_connect_cfm() puts the one from sco_conn_add() as
soon as sco_conn_ready() returns.
Discovered by XBOW, triaged by Baul Lee <baul.lee@xbow.com>
Reported privately to the maintainers on 2026-07-10 with root-cause
analysis, a PoC, a KASAN log and this fix; posting to the list was
requested as the follow-up.
Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn")
Reported-by: Federico Kirschbaum <federico.kirschbaum@xbow.com>
Reported-by: Baul Lee <baul.lee@xbow.com>
Cc: stable@vger.kernel.org
Signed-off-by: Baul Lee <baul.lee@xbow.com>
---
net/bluetooth/sco.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index c05f79b7aa31..3db6552de06c 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -276,6 +276,9 @@ static void sco_conn_del(struct hci_conn *hcon, int err)
sco_chan_del(sk, err);
release_sock(sk);
sock_put(sk);
+
+ /* Drop the association reference, as the !sk branch above does */
+ sco_conn_put(conn);
}
static void __sco_chan_add(struct sco_conn *conn, struct sock *sk,
@@ -296,10 +299,16 @@ static int sco_chan_add(struct sco_conn *conn, struct sock *sk,
int err = 0;
sco_conn_lock(conn);
- if (conn->sk || sco_pi(sk)->conn)
+ if (conn->sk || sco_pi(sk)->conn) {
err = -EBUSY;
- else
+ } else {
+ /* Take the socket reference, which sco_chan_del() drops when
+ * the socket detaches. Without it the socket and the hcon
+ * would share the single reference from sco_conn_add().
+ */
+ sco_conn_hold(conn);
__sco_chan_add(conn, sk, parent);
+ }
sco_conn_unlock(conn);
return err;
@@ -1452,6 +1461,13 @@ static void sco_conn_ready(struct sco_conn *conn)
bacpy(&sco_pi(sk)->src, &conn->hcon->src);
bacpy(&sco_pi(sk)->dst, &conn->hcon->dst);
+ /* Two references are needed here: the socket one, dropped by
+ * sco_chan_del(), and the association one, dropped by
+ * sco_conn_del(). Unlike the outgoing path, the reference
+ * from sco_conn_add() cannot serve as the latter, because
+ * sco_connect_cfm() puts it as soon as this function returns.
+ */
+ sco_conn_hold(conn);
sco_conn_hold(conn);
hci_conn_hold(conn->hcon);
__sco_chan_add(conn, sk, parent);
--
2.50.1 (Apple Git-155)
next reply other threads:[~2026-07-26 5:54 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-26 5:54 Baul Lee [this message]
2026-07-26 9:55 ` Bluetooth: SCO: fix sco_conn double free on outgoing connect bluez.test.bot
2026-07-26 10:15 ` [PATCH] " Pauli Virtanen
2026-07-26 13:37 ` Aldo Ariel Panzardo
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=20260726055431.42350-1-baul.lee@xbow.com \
--to=baul.lee@xbow.com \
--cc=federico.kirschbaum@xbow.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=stable@vger.kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.