Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: SCO: fix sco_conn double free on outgoing connect
@ 2026-07-26  5:54 Baul Lee
  2026-07-26  9:55 ` bluez.test.bot
  2026-07-26 10:15 ` [PATCH] " Pauli Virtanen
  0 siblings, 2 replies; 4+ messages in thread
From: Baul Lee @ 2026-07-26  5:54 UTC (permalink / raw)
  To: linux-bluetooth, linux-kernel
  Cc: luiz.dentz, marcel, federico.kirschbaum, Baul Lee, stable

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)


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-07-26 13:38 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-26  5:54 [PATCH] Bluetooth: SCO: fix sco_conn double free on outgoing connect Baul Lee
2026-07-26  9:55 ` bluez.test.bot
2026-07-26 10:15 ` [PATCH] " Pauli Virtanen
2026-07-26 13:37   ` Aldo Ariel Panzardo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox