Linux bluetooth development
 help / color / mirror / Atom feed
From: Pauli Virtanen <pav@iki.fi>
To: Baul Lee <baul.lee@xbow.com>,
	linux-bluetooth@vger.kernel.org,  linux-kernel@vger.kernel.org
Cc: luiz.dentz@gmail.com, marcel@holtmann.org,
	federico.kirschbaum@xbow.com, stable@vger.kernel.org,
	Your Name <qwe.aldo@gmail.com>
Subject: Re: [PATCH] Bluetooth: SCO: fix sco_conn double free on outgoing connect
Date: Sun, 26 Jul 2026 13:15:57 +0300	[thread overview]
Message-ID: <5e2a239a419898c87e80ed360b06e287225de00a.camel@iki.fi> (raw)
In-Reply-To: <20260726055431.42350-1-baul.lee@xbow.com>

Hi,

su, 2026-07-26 kello 14:54 +0900, Baul Lee kirjoitti:
> 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.

This appears to break closing SCO connections, indeed "SCO Disconnect -
Success" fails now, due to hci_conn_drop() not called on socket close
since one sco_conn refcount is now owned by hci_conn::sco_data.

The socket should own a hci_conn_hold/drop refcount that it drops when
closing.

Another option is to have only socket own sco_conn, like here
https://lore.kernel.org/linux-bluetooth/20260725195230.967546-1-qwe.aldo@gmail.com/

However, in that solution one would still need additional
synchronization to deal with the race between sco_conn_del(),
sco_conn_free(), sco_conn_add() vs. hci_conn::sco_data access
https://lore.kernel.org/linux-bluetooth/b84fd01c20f0d2975c7817da345a6937d67cf314.1784923689.git.pav@iki.fi/


In the patch here there seems to be some remaining UAF, could make
sense to try to address them while at it

sco_chan_del()				sco_conn_del()
  sco_conn_lock
  conn->sk = NULL
  sco_conn_unlock
					sk = ... /* = NULL */
  					if (!sk)
					  { sco_conn_put; return; }
					/* free hci_conn */
  sco_conn_put
    conn->hcon->sco_data = NULL /* UAF */

and

sco_sock_timeout()			sco_conn_del()
  conn = ...				conn = ...
  ...	  				...
					sco_conn_put()
					free hci_conn
  sco_conn_put()
    conn->hcon->sco_data = NULL /* UAF */

since access to sco_data is not serialized by any lock. Probably it can
be cleared in sco_conn_del() so it can get
__guarded_by(&hdev->lock) context analysis annotation
https://docs.kernel.org/dev-tools/context-analysis.html

> 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);

I think it would be stylistically better to have the holds where they
are assigned to the fields that own the references, and the puts where
the variable is cleared.

>  		hci_conn_hold(conn->hcon);
>  		__sco_chan_add(conn, sk, parent);

-- 
Pauli Virtanen

  parent reply	other threads:[~2026-07-26 10:16 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Pauli Virtanen [this message]
2026-07-26 13:37   ` [PATCH] " 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=5e2a239a419898c87e80ed360b06e287225de00a.camel@iki.fi \
    --to=pav@iki.fi \
    --cc=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=qwe.aldo@gmail.com \
    --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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox