mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] Bluetooth: SCO: give the socket its own sco_conn reference
@ 2026-07-23 23:29 Aldo Ariel Panzardo
  2026-07-25 11:37 ` Pauli Virtanen
  0 siblings, 1 reply; 5+ messages in thread
From: Aldo Ariel Panzardo @ 2026-07-23 23:29 UTC (permalink / raw)
  To: linux-bluetooth
  Cc: marcel, luiz.dentz, pav, linux-kernel, Aldo Ariel Panzardo, stable

sco_conn_del() drops a reference it does not own. It takes one transient
reference via sco_conn_hold_unless_zero() and releases it with the
sco_conn_put() that follows sco_sock_hold(); the additional put in the
!sk branch releases a second one:

    conn = sco_conn_hold_unless_zero(conn);
    ...
    sk = sco_sock_hold(conn);
    sco_conn_unlock(conn);
    sco_conn_put(conn);

    if (!sk) {
            sco_conn_put(conn);
            return;
    }

When close() races the controller's Disconnection Complete, sco_chan_del()
clears conn->sk and drops the socket's reference while sco_conn_del() is
running. sco_conn_del() then sees sk == NULL, its own put drops the count
to zero and frees the conn, and the second put writes to the freed kref:

    BUG: KASAN: slab-use-after-free in sco_conn_put.part.0+0x1a/0x190
    Write of size 4 at addr ffff8881099dec74 by task kworker/u17:3/413
    Workqueue: hci1 hci_rx_work
    Call Trace:
     sco_conn_put.part.0+0x1a/0x190
     hci_disconn_complete_evt+0x1ee/0x3e0
     hci_event_packet+0x54a/0x650
     hci_rx_work+0x321/0x3d0
    Allocated by task 413:
     sco_conn_add+0x72/0x1a0
     sco_connect_cfm+0x88/0x670
    Freed by task 413:
     sco_conn_del.isra.0+0x3f/0xf0
     hci_disconn_complete_evt+0x1ee/0x3e0
    refcount_t: underflow; use-after-free.

Simply deleting the extra put is not enough, because the reference it
releases is not always accounted for elsewhere. __sco_chan_add() stores
the connection in the socket without taking a reference:

    sco_pi(sk)->conn = conn;

so the socket inherits whatever reference its caller happened to hold.
That works out for sco_conn_ready(), which takes an explicit
sco_conn_hold() beforehand and whose caller puts its own reference, and
for the success path of sco_connect(), where the reference returned by
sco_conn_add() is silently handed over and later released by
sco_sock_destruct(). It does not work out for the two error paths of
sco_connect(): if the socket state changed while the lock was dropped, or
if sco_chan_add() returns -EBUSY, the reference from sco_conn_add() is
never released and the connection is leaked. The extra put in
sco_conn_del() is what eventually reclaims those orphans, which is why
removing it in isolation trades a use-after-free for a leak.

Make the ownership explicit instead. __sco_chan_add() now takes the
socket's reference itself, sco_connect() releases the one it got from
sco_conn_add() on every path, and the now redundant hold in
sco_conn_ready() is dropped. With the socket holding a counted reference,
a connection can no longer reach zero while conn->sk is set, so
sco_conn_free() no longer has to clear sco_pi(conn->sk)->conn. Every
reference then has exactly one owner: the one sco_conn_add() returns
belongs to its caller, the socket's is taken and released with the
channel, and sco_conn_del() and sco_sock_timeout() only ever hold
transient ones.

Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn")
Cc: stable@vger.kernel.org
Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@gmail.com>
---
v2:
 - Do not just delete the extra put: make the socket own its reference,
   balance sco_connect()'s error paths and drop the redundant hold in
   sco_conn_ready(), per Pauli Virtanen's review.
 - Drop the now unreachable sco_pi(conn->sk)->conn clearing in
   sco_conn_free().
 - Indent the quoted code with spaces so gitlint stops complaining.

On hci_conn_drop() vs hci_connect_sco(), which was also asked about: the
reference hci_connect_sco() returns is released by hci_conn_drop() on
each error path of sco_connect(), and on the success path it is handed to
the connection and released by sco_conn_free(). That side looks balanced.
There is a separate asymmetry that this patch does not touch: when
sco_conn_add() returns a connection that already existed for the hcon,
hci_connect_sco() has taken a fresh hci_conn reference but sco_conn_free()
only ever issues one hci_conn_drop(). That looks like a pre-existing
hci_conn leak rather than an sco_conn one; I did not want to fold it into
this fix.

Testing: the original defect reproduced 45 times across 2 independent
runs on unmodified v7.2-rc1-240-g71dfdfb0209b with KASAN, driven through
/dev/vhci by racing close() of an SCO socket against an injected
Disconnection Complete; both KASAN and the refcount_t underflow fired
every time. The BlueZ CI ran sco-tester against v1 with no regression.

 net/bluetooth/sco.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index fcc597be5bbd..21f829575803 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -81,9 +81,6 @@ static void sco_conn_free(struct kref *r
 
 	BT_DBG("conn %p", conn);
 
-	if (conn->sk)
-		sco_pi(conn->sk)->conn = NULL;
-
 	if (conn->hcon) {
 		conn->hcon->sco_data = NULL;
 		hci_conn_drop(conn->hcon);
@@ -265,10 +262,8 @@ static void sco_conn_del(struct hci_conn
 	sco_conn_unlock(conn);
 	sco_conn_put(conn);
 
-	if (!sk) {
-		sco_conn_put(conn);
+	if (!sk)
 		return;
-	}
 
 	/* Kill socket */
 	lock_sock(sk);
@@ -283,7 +278,7 @@ static void __sco_chan_add(struct sco_co
 {
 	BT_DBG("conn %p", conn);
 
-	sco_pi(sk)->conn = conn;
+	sco_pi(sk)->conn = sco_conn_hold(conn);
 	conn->sk = sk;
 
 	if (parent)
@@ -366,12 +361,14 @@ static int sco_connect(struct sock *sk)
 	 */
 	if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) {
 		release_sock(sk);
+		sco_conn_put(conn);
 		hci_conn_drop(hcon);
 		err = -EBADFD;
 		goto unlock;
 	}
 
 	err = sco_chan_add(conn, sk, NULL);
+	sco_conn_put(conn);
 	if (err) {
 		release_sock(sk);
 		hci_conn_drop(hcon);
@@ -1439,7 +1436,6 @@ static void sco_conn_ready(struct sco_co
 		bacpy(&sco_pi(sk)->src, &conn->hcon->src);
 		bacpy(&sco_pi(sk)->dst, &conn->hcon->dst);
 
-		sco_conn_hold(conn);
 		hci_conn_hold(conn->hcon);
 		__sco_chan_add(conn, sk, parent);
 
-- 
2.43.0
 
 

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

* Re: [PATCH v2] Bluetooth: SCO: give the socket its own sco_conn reference
  2026-07-23 23:29 [PATCH v2] Bluetooth: SCO: give the socket its own sco_conn reference Aldo Ariel Panzardo
@ 2026-07-25 11:37 ` Pauli Virtanen
  2026-07-25 19:52   ` [PATCH v3] " Aldo Ariel Panzardo
  2026-07-25 19:52   ` [PATCH v2] " Aldo Ariel Panzardo
  0 siblings, 2 replies; 5+ messages in thread
From: Pauli Virtanen @ 2026-07-25 11:37 UTC (permalink / raw)
  To: Aldo Ariel Panzardo, linux-bluetooth
  Cc: marcel, luiz.dentz, linux-kernel, stable

Hi,

to, 2026-07-23 kello 20:29 -0300, Aldo Ariel Panzardo kirjoitti:
> sco_conn_del() drops a reference it does not own. It takes one transient
> reference via sco_conn_hold_unless_zero() and releases it with the
> sco_conn_put() that follows sco_sock_hold(); the additional put in the
> !sk branch releases a second one:
> 
>     conn = sco_conn_hold_unless_zero(conn);
>     ...
>     sk = sco_sock_hold(conn);
>     sco_conn_unlock(conn);
>     sco_conn_put(conn);
> 
>     if (!sk) {
>             sco_conn_put(conn);
>             return;
>     }
> 
> When close() races the controller's Disconnection Complete, sco_chan_del()
> clears conn->sk and drops the socket's reference while sco_conn_del() is
> running. sco_conn_del() then sees sk == NULL, its own put drops the count
> to zero and frees the conn, and the second put writes to the freed kref:
> 
>     BUG: KASAN: slab-use-after-free in sco_conn_put.part.0+0x1a/0x190
>     Write of size 4 at addr ffff8881099dec74 by task kworker/u17:3/413
>     Workqueue: hci1 hci_rx_work
>     Call Trace:
>      sco_conn_put.part.0+0x1a/0x190
>      hci_disconn_complete_evt+0x1ee/0x3e0
>      hci_event_packet+0x54a/0x650
>      hci_rx_work+0x321/0x3d0
>     Allocated by task 413:
>      sco_conn_add+0x72/0x1a0
>      sco_connect_cfm+0x88/0x670
>     Freed by task 413:
>      sco_conn_del.isra.0+0x3f/0xf0
>      hci_disconn_complete_evt+0x1ee/0x3e0
>     refcount_t: underflow; use-after-free.
> 
> Simply deleting the extra put is not enough, because the reference it
> releases is not always accounted for elsewhere. __sco_chan_add() stores
> the connection in the socket without taking a reference:
> 
>     sco_pi(sk)->conn = conn;
> 
> so the socket inherits whatever reference its caller happened to hold.
> That works out for sco_conn_ready(), which takes an explicit
> sco_conn_hold() beforehand and whose caller puts its own reference, and
> for the success path of sco_connect(), where the reference returned by
> sco_conn_add() is silently handed over and later released by
> sco_sock_destruct(). It does not work out for the two error paths of
> sco_connect(): if the socket state changed while the lock was dropped, or
> if sco_chan_add() returns -EBUSY, the reference from sco_conn_add() is
> never released and the connection is leaked. The extra put in
> sco_conn_del() is what eventually reclaims those orphans, which is why
> removing it in isolation trades a use-after-free for a leak.
> 
> Make the ownership explicit instead. __sco_chan_add() now takes the
> socket's reference itself, sco_connect() releases the one it got from
> sco_conn_add() on every path, and the now redundant hold in
> sco_conn_ready() is dropped. With the socket holding a counted reference,
> a connection can no longer reach zero while conn->sk is set, so
> sco_conn_free() no longer has to clear sco_pi(conn->sk)->conn. Every
> reference then has exactly one owner: the one sco_conn_add() returns
> belongs to its caller, the socket's is taken and released with the
> channel, and sco_conn_del() and sco_sock_timeout() only ever hold
> transient ones.
> 
> Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn")
> Cc: stable@vger.kernel.org
> Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@gmail.com>
> ---
> v2:
>  - Do not just delete the extra put: make the socket own its reference,
>    balance sco_connect()'s error paths and drop the redundant hold in
>    sco_conn_ready(), per Pauli Virtanen's review.
>  - Drop the now unreachable sco_pi(conn->sk)->conn clearing in
>    sco_conn_free().
>  - Indent the quoted code with spaces so gitlint stops complaining.
> 
> On hci_conn_drop() vs hci_connect_sco(), which was also asked about: the
> reference hci_connect_sco() returns is released by hci_conn_drop() on
> each error path of sco_connect(), and on the success path it is handed to
> the connection and released by sco_conn_free(). That side looks balanced.
> There is a separate asymmetry that this patch does not touch: when
> sco_conn_add() returns a connection that already existed for the hcon,
> hci_connect_sco() has taken a fresh hci_conn reference but sco_conn_free()
> only ever issues one hci_conn_drop(). That looks like a pre-existing
> hci_conn leak rather than an sco_conn one; I did not want to fold it into
> this fix.

There's a double drop on the error paths now, so it'll probably hit
WARN_ON() if these are reached, but it's not fatal.

The SCO refcounting rule probably should be that sco_conn owns one
hci_conn_hold reference all of its lifetime.

hci_connect_sco() returns a hci_conn with a new hci_conn_hold reference
given to the caller.

sco_connect_cfm() never gives callee a hci_conn_hold refcount.

So probably (this will have to be thought out better and maybe separate
patch):

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index 5cab7e2fb898..0e1dd6a8e6d7 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -185,6 +185,8 @@ static void sco_sock_clear_timer(struct sock *sk)
 }
 
 /* ---- SCO connections ---- */
+
+/* Consumes hci_conn_hold refcount */
 static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
 {
 	struct sco_conn *conn = hcon->sco_data;
@@ -195,6 +197,9 @@ static struct sco_conn *sco_conn_add(struct
hci_conn *hcon)
 			sco_conn_lock(conn);
 			conn->hcon = hcon;
 			sco_conn_unlock(conn);
+		} else {
+			/* We already own the refcount */
+			hci_conn_drop(hcon);
 		}
 		return conn;
 	}
@@ -362,7 +367,6 @@ static int sco_connect(struct sock *sk)
 	if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) {
 		release_sock(sk);
 		sco_conn_put(conn);
-		hci_conn_drop(hcon);
 		err = -EBADFD;
 		goto unlock;
 	}
@@ -371,7 +375,6 @@ static int sco_connect(struct sock *sk)
 	sco_conn_put(conn);
 	if (err) {
 		release_sock(sk);
-		hci_conn_drop(hcon);
 		goto unlock;
 	}
 
@@ -1449,7 +1452,6 @@ 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);
 
-		hci_conn_hold(conn->hcon);
 		__sco_chan_add(conn, sk, parent);
 
 		if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)-
>flags))
@@ -1505,7 +1507,7 @@ static void sco_connect_cfm(struct hci_conn
*hcon, __u8 status)
 	if (!status) {
 		struct sco_conn *conn;
 
-		conn = sco_conn_add(hcon);
+		conn = sco_conn_add(hci_conn_hold(hcon));
 		if (conn) {
 			sco_conn_ready(conn);
 			sco_conn_put(conn);

> 
> Testing: the original defect reproduced 45 times across 2 independent
> runs on unmodified v7.2-rc1-240-g71dfdfb0209b with KASAN, driven through
> /dev/vhci by racing close() of an SCO socket against an injected
> Disconnection Complete; both KASAN and the refcount_t underflow fired
> every time. The BlueZ CI ran sco-tester against v1 with no regression.
> 
>  net/bluetooth/sco.c | 12 ++++--------
>  1 file changed, 4 insertions(+), 8 deletions(-)
> 
> diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
> index fcc597be5bbd..21f829575803 100644
> --- a/net/bluetooth/sco.c
> +++ b/net/bluetooth/sco.c
> @@ -81,9 +81,6 @@ static void sco_conn_free(struct kref *r
>  
>  	BT_DBG("conn %p", conn);
>  
> -	if (conn->sk)
> -		sco_pi(conn->sk)->conn = NULL;
> -
>  	if (conn->hcon) {
>  		conn->hcon->sco_data = NULL;
>  		hci_conn_drop(conn->hcon);
> @@ -265,10 +262,8 @@ static void sco_conn_del(struct hci_conn
>  	sco_conn_unlock(conn);
>  	sco_conn_put(conn);
>  
> -	if (!sk) {
> -		sco_conn_put(conn);
> +	if (!sk)
>  		return;
> -	}
>  
>  	/* Kill socket */
>  	lock_sock(sk);
> @@ -283,7 +278,7 @@ static void __sco_chan_add(struct sco_co
>  {
>  	BT_DBG("conn %p", conn);
>  
> -	sco_pi(sk)->conn = conn;
> +	sco_pi(sk)->conn = sco_conn_hold(conn);
>  	conn->sk = sk;
>  
>  	if (parent)
> @@ -366,12 +361,14 @@ static int sco_connect(struct sock *sk)
>  	 */
>  	if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) {
>  		release_sock(sk);
> +		sco_conn_put(conn);
>  		hci_conn_drop(hcon);
>  		err = -EBADFD;
>  		goto unlock;
>  	}
>  
>  	err = sco_chan_add(conn, sk, NULL);
> +	sco_conn_put(conn);
>  	if (err) {
>  		release_sock(sk);
>  		hci_conn_drop(hcon);
> @@ -1439,7 +1436,6 @@ static void sco_conn_ready(struct sco_co
>  		bacpy(&sco_pi(sk)->src, &conn->hcon->src);
>  		bacpy(&sco_pi(sk)->dst, &conn->hcon->dst);
>  
> -		sco_conn_hold(conn);
>  		hci_conn_hold(conn->hcon);
>  		__sco_chan_add(conn, sk, parent);
>  

-- 
Pauli Virtanen

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

* [PATCH v3] Bluetooth: SCO: give the socket its own sco_conn reference
  2026-07-25 11:37 ` Pauli Virtanen
@ 2026-07-25 19:52   ` Aldo Ariel Panzardo
  2026-07-27 20:50     ` patchwork-bot+bluetooth
  2026-07-25 19:52   ` [PATCH v2] " Aldo Ariel Panzardo
  1 sibling, 1 reply; 5+ messages in thread
From: Aldo Ariel Panzardo @ 2026-07-25 19:52 UTC (permalink / raw)
  To: Pauli Virtanen, Luiz Augusto von Dentz
  Cc: marcel, linux-bluetooth, linux-kernel, stable, Aldo Ariel Panzardo

sco_conn_del() drops a reference it does not own. It takes one transient
reference via sco_conn_hold_unless_zero() and releases it with the
sco_conn_put() that follows sco_sock_hold(); the additional put in the
!sk branch releases a second one:

    conn = sco_conn_hold_unless_zero(conn);
    ...
    sk = sco_sock_hold(conn);
    sco_conn_unlock(conn);
    sco_conn_put(conn);

    if (!sk) {
            sco_conn_put(conn);
            return;
    }

When close() races the controller's Disconnection Complete, sco_chan_del()
clears conn->sk and drops the socket's reference while sco_conn_del() is
running. sco_conn_del() then sees sk == NULL, its own put drops the count
to zero and frees the conn, and the second put writes to the freed kref:

    BUG: KASAN: slab-use-after-free in sco_conn_put.part.0+0x1a/0x190
    Write of size 4 at addr ffff8881099dec74 by task kworker/u17:3/413
    Workqueue: hci1 hci_rx_work
    Call Trace:
     sco_conn_put.part.0+0x1a/0x190
     hci_disconn_complete_evt+0x1ee/0x3e0
     hci_event_packet+0x54a/0x650
     hci_rx_work+0x321/0x3d0
    Allocated by task 413:
     sco_conn_add+0x72/0x1a0
     sco_connect_cfm+0x88/0x670
    Freed by task 413:
     sco_conn_del.isra.0+0x3f/0xf0
     hci_disconn_complete_evt+0x1ee/0x3e0
    refcount_t: underflow; use-after-free.

The root cause is that the socket stores the connection without holding a
reference of its own. __sco_chan_add() does:

    sco_pi(sk)->conn = conn;

so the socket borrows whatever reference its caller happened to hold, and
the callers paper over that with ad-hoc holds and puts. Give the socket a
counted reference instead: __sco_chan_add() takes one and it is released
together with the channel (sco_chan_del()) and in sco_sock_destruct().
With the socket holding its own reference, sco_conn_del() no longer needs
the extra put and the redundant hold in sco_conn_ready() goes away.

Making the socket own its reference means the connection is now actually
freed on the error paths of sco_connect() where it used to leak, which in
turn runs sco_conn_free() and its hci_conn_drop(conn->hcon). To keep the
hci_conn accounting balanced, make that ownership explicit as well:
sco_conn_add() consumes one hci_conn reference and the sco_conn owns it for
its lifetime. sco_connect() hands over the reference returned by
hci_connect_sco() and no longer drops it on the error paths;
sco_connect_cfm(), which is not given a reference, takes one with
hci_conn_hold() before handing it to sco_conn_add() (and drops it again if
the allocation fails); and the explicit hci_conn_hold() in sco_conn_ready()
is removed. Every reference then has a single, clear owner.

Fixes: e6720779ae61 ("Bluetooth: SCO: Use kref to track lifetime of sco_conn")
Cc: stable@vger.kernel.org
Suggested-by: Pauli Virtanen <pav@iki.fi>
Signed-off-by: Aldo Ariel Panzardo <qwe.aldo@gmail.com>
---
v3:
 - Incorporate Pauli Virtanen's review: make sco_conn own one hci_conn
   reference for its whole lifetime -- sco_conn_add() consumes an hci_conn
   reference, sco_connect() no longer drops hcon on its error paths,
   sco_connect_cfm() holds one before sco_conn_add(), and the hci_conn_hold()
   in sco_conn_ready() is removed. This avoids the double hci_conn_drop() on
   sco_connect()'s error paths that v2 would otherwise introduce.
 - Keep the sco_pi(conn->sk)->conn = NULL clearing in sco_conn_free(). v2
   removed it as unreachable, but KASAN testing of close() racing the
   Disconnection Complete showed that dropping it reintroduces a
   use-after-free on the sco_sock_release() path, so it is retained.
 - Drop the hcon reference in sco_connect_cfm() when sco_conn_add() fails,
   so the allocation-failure path does not leak it.
v2:
 - Make the socket own its sco_conn reference rather than only deleting the
   extra put, per Pauli Virtanen's review.

Testing: on v7.2-rc4 with KASAN and a /dev/vhci reproducer that races
close() of an SCO socket against an injected Disconnection Complete, the
unpatched kernel hits the refcount_t underflow / use-after-free above
within a few thousand iterations; with this patch the sco_conn_del()
over-put on the Disconnection Complete path no longer reproduces.

 net/bluetooth/sco.c | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index fcc597be5bbd..aa9f61a748ab 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -188,6 +188,9 @@ static void sco_sock_clear_timer(struct sock *sk)
 }
 
 /* ---- SCO connections ---- */
+/* Consumes a reference on @hcon, which the returned sco_conn owns until it is
+ * freed. On failure (NULL return) the reference is left for the caller to drop.
+ */
 static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
 {
 	struct sco_conn *conn = hcon->sco_data;
@@ -198,6 +201,9 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
 			sco_conn_lock(conn);
 			conn->hcon = hcon;
 			sco_conn_unlock(conn);
+		} else {
+			/* conn already owns a reference on hcon */
+			hci_conn_drop(hcon);
 		}
 		return conn;
 	}
@@ -265,10 +271,8 @@ static void sco_conn_del(struct hci_conn *hcon, int err)
 	sco_conn_unlock(conn);
 	sco_conn_put(conn);
 
-	if (!sk) {
-		sco_conn_put(conn);
+	if (!sk)
 		return;
-	}
 
 	/* Kill socket */
 	lock_sock(sk);
@@ -283,7 +287,7 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk,
 {
 	BT_DBG("conn %p", conn);
 
-	sco_pi(sk)->conn = conn;
+	sco_pi(sk)->conn = sco_conn_hold(conn);
 	conn->sk = sk;
 
 	if (parent)
@@ -366,15 +370,15 @@ static int sco_connect(struct sock *sk)
 	 */
 	if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND) {
 		release_sock(sk);
-		hci_conn_drop(hcon);
+		sco_conn_put(conn);
 		err = -EBADFD;
 		goto unlock;
 	}
 
 	err = sco_chan_add(conn, sk, NULL);
+	sco_conn_put(conn);
 	if (err) {
 		release_sock(sk);
-		hci_conn_drop(hcon);
 		goto unlock;
 	}
 
@@ -1439,8 +1443,6 @@ 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);
 
-		sco_conn_hold(conn);
-		hci_conn_hold(conn->hcon);
 		__sco_chan_add(conn, sk, parent);
 
 		if (test_bit(BT_SK_DEFER_SETUP, &bt_sk(parent)->flags))
@@ -1496,10 +1498,12 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status)
 	if (!status) {
 		struct sco_conn *conn;
 
-		conn = sco_conn_add(hcon);
+		conn = sco_conn_add(hci_conn_hold(hcon));
 		if (conn) {
 			sco_conn_ready(conn);
 			sco_conn_put(conn);
+		} else {
+			hci_conn_drop(hcon);
 		}
 	} else
 		sco_conn_del(hcon, bt_to_errno(status));
-- 
2.43.0


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

* Re: [PATCH v2] Bluetooth: SCO: give the socket its own sco_conn reference
  2026-07-25 11:37 ` Pauli Virtanen
  2026-07-25 19:52   ` [PATCH v3] " Aldo Ariel Panzardo
@ 2026-07-25 19:52   ` Aldo Ariel Panzardo
  1 sibling, 0 replies; 5+ messages in thread
From: Aldo Ariel Panzardo @ 2026-07-25 19:52 UTC (permalink / raw)
  To: Pauli Virtanen
  Cc: Aldo Ariel Panzardo, Luiz Augusto von Dentz, marcel,
	linux-bluetooth, linux-kernel

Hi Pauli,

Thanks a lot for the detailed review and for sketching the hci_conn
ownership rule -- v3 incorporates it (you're on the patch as Suggested-by):
sco_conn_add() now consumes one hci_conn reference and the sco_conn owns it
for its whole lifetime, sco_connect() no longer drops hcon on its error
paths, sco_connect_cfm() takes a reference before sco_conn_add() (and drops
it again if the allocation fails), and the hci_conn_hold() in
sco_conn_ready() is gone. That removes the double hci_conn_drop() you
spotted on the error paths.

One correction to v2: I had also dropped the

	if (conn->sk)
		sco_pi(conn->sk)->conn = NULL;

clearing in sco_conn_free() as "unreachable". v3 keeps it. Testing the
close()-vs-Disconnection-Complete race under KASAN showed that removing it
reintroduces a use-after-free on the sco_sock_release() path, so it stays.

While validating this I built a /dev/vhci reproducer that races close() of
an SCO socket against an injected Disconnection Complete over many
iterations, on v7.2-rc4 with KASAN. It reproduces the sco_conn_del()
over-put reliably on an unmodified tree (a few thousand iterations), and
with this patch that over-put no longer fires.

I want to be upfront that it does not leave the reproducer fully clean,
though. With the over-put fixed, the same race still occasionally trips a
separate use-after-free in the SCODATA receive path, which this patch does
not touch:

    BUG: KASAN: slab-use-after-free in sco_conn_hold_unless_zero+0xbe/0x160
    Write of size 4 by task kworker/u17:0
    Workqueue: hci0 hci_rx_work
    Call Trace:
     sco_conn_hold_unless_zero+0xbe/0x160
     sco_recv_scodata+0x13f/0x490
     hci_rx_work+0x3af/0x730

sco_recv_scodata() takes conn from hcon->sco_data and calls
sco_conn_hold_unless_zero() on it, but kref_get_unless_zero() only guards
against a zero refcount, not against the sco_conn already having been
freed and its memory reclaimed -- the read of the refcount itself is the
UAF. It looks like a pre-existing race around the hcon->sco_data weak
pointer rather than something introduced here, but I haven't yet isolated
it on an otherwise-unmodified tree, so I didn't want to fold a fix into
this patch blind.

Happy to dig into that one next (it feels like the sco_conn lifetime wants
a firmer owner for hcon->sco_data), either as a follow-up or however you'd
prefer to see it handled. Let me know if you'd rather I respin this
together with that.

Thanks,
Aldo

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

* Re: [PATCH v3] Bluetooth: SCO: give the socket its own sco_conn reference
  2026-07-25 19:52   ` [PATCH v3] " Aldo Ariel Panzardo
@ 2026-07-27 20:50     ` patchwork-bot+bluetooth
  0 siblings, 0 replies; 5+ messages in thread
From: patchwork-bot+bluetooth @ 2026-07-27 20:50 UTC (permalink / raw)
  To: Aldo Ariel Panzardo
  Cc: pav, luiz.dentz, marcel, linux-bluetooth, linux-kernel, stable

Hello:

This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Sat, 25 Jul 2026 16:52:30 -0300 you wrote:
> sco_conn_del() drops a reference it does not own. It takes one transient
> reference via sco_conn_hold_unless_zero() and releases it with the
> sco_conn_put() that follows sco_sock_hold(); the additional put in the
> !sk branch releases a second one:
> 
>     conn = sco_conn_hold_unless_zero(conn);
>     ...
>     sk = sco_sock_hold(conn);
>     sco_conn_unlock(conn);
>     sco_conn_put(conn);
> 
> [...]

Here is the summary with links:
  - [v3] Bluetooth: SCO: give the socket its own sco_conn reference
    https://git.kernel.org/bluetooth/bluetooth-next/c/018d1e023ea5

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-07-27 20:50 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-23 23:29 [PATCH v2] Bluetooth: SCO: give the socket its own sco_conn reference Aldo Ariel Panzardo
2026-07-25 11:37 ` Pauli Virtanen
2026-07-25 19:52   ` [PATCH v3] " Aldo Ariel Panzardo
2026-07-27 20:50     ` patchwork-bot+bluetooth
2026-07-25 19:52   ` [PATCH v2] " 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

all inboxes | Powered by JetHome®