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
next prev 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