* [PATCH v3] Bluetooth: SCO: serialise sco_conn lifetime against sco_recv_scodata()
@ 2026-09-23 12:20 Aldo Ariel Panzardo
0 siblings, 0 replies; only message in thread
From: Aldo Ariel Panzardo @ 2026-09-23 12:20 UTC (permalink / raw)
To: pav, luiz.dentz
Cc: marcel, linux-bluetooth, linux-kernel, stable, Aldo Ariel Panzardo
sco_recv_scodata() upgrades the weak hcon->sco_data back-pointer to a
strong reference under hdev->lock:
hci_dev_lock(hdev);
hcon = hci_conn_hash_lookup_handle(hdev, handle);
...
conn = sco_conn_hold_unless_zero(hcon->sco_data);
hci_dev_unlock(hdev);
but the pointer is cleared from the other side without that lock. When
the last sco_conn reference is dropped, sco_conn_free() ran
conn->hcon->sco_data = NULL;
with no hdev->lock held, so the RX path could read hcon->sco_data and
call kref_get_unless_zero() on an sco_conn that was concurrently freed:
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
kref_get_unless_zero() only guards against a zero refcount, not against
the backing memory already being freed.
Give hcon->sco_data an actual reference on the sco_conn it points to, so
the object cannot be freed while the pointer is still installed, and only
clear and drop it from sco_conn_del(), which runs under hdev->lock (its
callers, sco_connect_cfm() and sco_disconn_cfm(), hold it). The reader in
sco_recv_scodata() also takes hdev->lock, so the store and the read are
now serialised: the RX path either observes NULL or a reference that is
guaranteed to stay valid until it drops its own.
sco_conn_add() no longer hands its kref_init() reference to the caller as
the connection's only reference; that initial reference is the one owned
by hcon->sco_data, and callers get their own via sco_conn_hold().
Because the sco_conn can now outlive the socket (the association keeps it
alive until the link goes down), the hci_conn can no longer be dropped
from sco_conn_free() without regressing socket close: closing a connected
SCO socket must still tear the link down. Move hci_conn ownership to the
socket instead: __sco_chan_add() takes an hci_conn reference and it is
released from sco_chan_del() and sco_sock_destruct(), exactly once. The
sco_conn no longer owns an hci_conn reference, so sco_conn_add() stops
consuming one and sco_connect() drops the hci_connect_sco() reference on
every path; sco_connect_cfm() no longer needs its hci_conn_hold() dance.
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: fix two UAFs found by Pauli Virtanen in the v2 review:
- sco_chan_del(): drop hci_conn ref before clearing conn->sk so that
sco_conn_del() on another CPU cannot free hci_conn under our feet
- sco_sock_destruct(): same ordering fix
- remove dead branch in sco_conn_add() (conn->hcon == NULL impossible)
- move hci_conn_drop in sco_connect() tighter after __sco_chan_add()
v2: quote inlined code and KASAN splat with spaces (bluez CI gitlint);
no functional change.
diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index 3d4362a..a2f17ab 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -84,12 +84,14 @@ static void sco_conn_free(struct kref *ref)
if (conn->sk)
sco_pi(conn->sk)->conn = NULL;
- if (conn->hcon) {
- conn->hcon->sco_data = NULL;
- hci_conn_drop(conn->hcon);
- }
+ /* hcon->sco_data is cleared and the association's reference on the
+ * sco_conn is dropped in sco_conn_del() under hdev->lock, and the
+ * hci_conn is now owned by the socket (held in __sco_chan_add() and
+ * dropped in sco_chan_del()/sco_sock_destruct()), so there is nothing
+ * left to release towards hcon here.
+ */
- /* Ensure no more work items will run since hci_conn has been dropped */
+ /* Ensure no more work items will run before the connection is freed */
disable_delayed_work_sync(&conn->timeout_work);
kfree(conn);
@@ -188,25 +190,19 @@ 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.
+/* Returns a new reference the caller must drop with sco_conn_put(). The
+ * hcon->sco_data association holds its own reference on the sco_conn for the
+ * connection's lifetime; it is dropped in sco_conn_del() under hdev->lock.
+ * @hcon is not consumed: the hci_conn reference is taken and owned by the
+ * socket in __sco_chan_add().
*/
static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
{
struct sco_conn *conn = hcon->sco_data;
conn = sco_conn_hold_unless_zero(conn);
- if (conn) {
- if (!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);
- }
+ if (conn)
return conn;
- }
conn = kzalloc_obj(struct sco_conn);
if (!conn)
@@ -227,7 +223,10 @@ static struct sco_conn *sco_conn_add(struct hci_conn *hcon)
BT_DBG("hcon %p conn %p", hcon, conn);
- return conn;
+ /* kref_init() above set the association reference owned by
+ * hcon->sco_data; hand the caller its own reference.
+ */
+ return sco_conn_hold(conn);
}
/* Delete channel.
@@ -242,6 +241,19 @@ static void sco_chan_del(struct sock *sk, int err)
BT_DBG("sk %p, conn %p, err %d", sk, conn, err);
if (conn) {
+ struct hci_conn *hcon;
+
+ sco_conn_lock(conn);
+ hcon = conn->hcon;
+ sco_conn_unlock(conn);
+
+ /* Drop the socket's hci_conn reference BEFORE clearing
+ * conn->sk, so sco_conn_del() on another CPU cannot free
+ * the hci_conn while we still hold a pointer to it.
+ */
+ if (hcon)
+ hci_conn_drop(hcon);
+
sco_conn_lock(conn);
conn->sk = NULL;
sco_conn_unlock(conn);
@@ -266,6 +278,13 @@ static void sco_conn_del(struct hci_conn *hcon, int err)
BT_DBG("hcon %p conn %p, err %d", hcon, conn, err);
+ /* Detach from the hci_conn and drop the association's reference.
+ * The caller holds hdev->lock, which serialises this against the
+ * read of hcon->sco_data in sco_recv_scodata().
+ */
+ hcon->sco_data = NULL;
+ sco_conn_put(conn);
+
sco_conn_lock(conn);
sk = sco_sock_hold(conn);
sco_conn_unlock(conn);
@@ -290,6 +309,11 @@ static void __sco_chan_add(struct sco_conn *conn, struct sock *sk,
sco_pi(sk)->conn = sco_conn_hold(conn);
conn->sk = sk;
+ /* The socket owns an hci_conn reference for as long as it stays
+ * attached; it is dropped in sco_chan_del()/sco_sock_destruct().
+ */
+ hci_conn_hold(conn->hcon);
+
if (parent)
bt_accept_enqueue(parent, sk, true);
}
@@ -371,6 +395,7 @@ 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;
}
@@ -379,9 +404,13 @@ static int sco_connect(struct sock *sk)
sco_conn_put(conn);
if (err) {
release_sock(sk);
+ hci_conn_drop(hcon);
goto unlock;
}
+ /* __sco_chan_add() took its own hci_conn reference; drop ours. */
+ hci_conn_drop(hcon);
+
/* Update source addr of the socket */
bacpy(&sco_pi(sk)->src, &hcon->src);
@@ -495,9 +524,26 @@ static struct sock *sco_get_sock_listen(bdaddr_t *src)
static void sco_sock_destruct(struct sock *sk)
{
+ struct sco_conn *conn = sco_pi(sk)->conn;
+
BT_DBG("sk %p", sk);
- sco_conn_put(sco_pi(sk)->conn);
+ /* If the channel was not already torn down via sco_chan_del(), drop
+ * the socket's own references here.
+ */
+ if (conn) {
+ struct hci_conn *hcon;
+
+ sco_conn_lock(conn);
+ hcon = conn->hcon;
+ sco_conn_unlock(conn);
+
+ if (hcon)
+ hci_conn_drop(hcon);
+ sco_pi(sk)->conn = NULL;
+ sco_conn_put(conn);
+ return;
+ }
skb_queue_purge(&sk->sk_receive_queue);
skb_queue_purge(&sk->sk_write_queue);
@@ -1511,12 +1557,10 @@ static void sco_connect_cfm(struct hci_conn *hcon, __u8 status)
if (!status) {
struct sco_conn *conn;
- conn = sco_conn_add(hci_conn_hold(hcon));
+ conn = sco_conn_add(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] only message in thread
only message in thread, other threads:[~2026-09-23 12:20 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23 12:20 [PATCH v3] Bluetooth: SCO: serialise sco_conn lifetime against sco_recv_scodata() 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®