From nobody Fri Jul 24 21:52:33 2026 Received: from mail-ej1-f44.google.com (mail-ej1-f44.google.com [209.85.218.44]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 550883612E7 for ; Thu, 23 Jul 2026 23:29:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.44 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784849358; cv=none; b=uU5XI3825oi9qz31Tb9pajDGVIKoE8n7kja1qUIudtJs+kOwQYmwjcZFbg5BzrO03zFs1onEZY5EVz+WaTGhol5YYIGm66lNAJVo448+pesNfoImXUVIYI+/DbcSoo+CSUS/L/eLe0JqJ7Y+anTpKXlHYe8mV/yOyZ/16P1LPn0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784849358; c=relaxed/simple; bh=GtyBnUDy1XmyW8TFUweRrkgz87Vb+jaYYX7eemGWk2w=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=NKkvxUsne4EZ3lTXL69ctRGa03GIM/YRHuAklbTO9pXOoLcQZJ0Q2zHZuVIpsDgHDdx+pzonDPMFt2nQNdSOrt2mK41Fhdj8FKvxUgLzeoyNejaij+CRLpA2ft0tHlJI8rMQKYKnI7UtC0EVBF6nAepaI/1GcORkagLFLV15fE0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=GbfAuAd8; arc=none smtp.client-ip=209.85.218.44 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="GbfAuAd8" Received: by mail-ej1-f44.google.com with SMTP id a640c23a62f3a-c15e2dab83eso202715066b.1 for ; Thu, 23 Jul 2026 16:29:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784849354; x=1785454154; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=IMll30OycGqJfNeIH/aMl3dCus/QWYYNzJKzRVgh/s0=; b=GbfAuAd8HuO4JClPJwwcKlxHx5M89C+Eq9DWVnp7p4Y/495E4Y3AsD0WljOk8XyCic /Etq8auNpFT7EcELKnnEnBGRupSAGSd+qPqF3JeM87PaGXyk6ipszHZJdqJ0UVQbFlXE 5d8zJj0QzMULjX7O+bthw+J+mRFDDL+ZpQ+9Yd6OFBPwSW7nX44P5xyoWLm7KUAD3P9x GaVL+LjHw/9QCnOkt5zpdQlyBmyolvTj6Iy3i42B6wkjR2knM3bUG0scDXbVfRfrBunJ caaLiNapaAQMPikLxKp/xbTcYr799GvenH1tFY4mY1Ycr7RjJoXPrgsFrlQTRdcFyWUp R7EQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784849354; x=1785454154; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=IMll30OycGqJfNeIH/aMl3dCus/QWYYNzJKzRVgh/s0=; b=hwmWCD471xxuZ51ZvYUgjkqt8NRRGhe/bUwaqCIfrYFHMDbFUAtJ5iDVg4NfxaaLU5 +hxRnbTESGidNpHYuzbSqYmKaU8hArFuMoCimRIjvC9d/Bgs+msNb0zlbTFv8uaEjJ0g eDOjgE6SJraMmQhjZu4VL05JPXP7Us9hbASXvOHz2zInCcyzZh6WNv2+0TwCqicC5aW0 htlxO7dCRhaYhYZ3/T5zP8+cE7/3arDzS1PgNdQWHN4cNSLopGAbFVvULnNM7sQa0Hpj DlxsmUoJHsbVRR3VT7kQ7mBCgXnJ/DPmo4atLpLLXUsmbZX40vwy5t8TgeP5zLxyu/yb wUSw== X-Forwarded-Encrypted: i=1; AHgh+RpOr8mWVovyUP40NREWIPrZJ2/nQ8UBinZt9FemO50Oier9YqsZ+o3RPm2fc8e6o/EFHB4IgdCXagtrYUU=@vger.kernel.org X-Gm-Message-State: AOJu0Yy44wbm5NWXWMF9IS8YZ3YyCIFwOuxdP4BVcr28jtL+bZQnhvRq I0AtNfbciwLE/KU8GBtzixhwW/YzJPhhtqlw5cHZEzF3mDXs6YsdaSco X-Gm-Gg: AR+sD12dna2JDoS0yd5l3nSOhttI3BYsm9Y41rs4sWY/AATP0/0jKb1XAijUcJrdPH8 DGa51cRJj2h/yUXRsXDp5qz/z4ETYyGXd7kxEgwG1rqo2C2Jf6LbEg6LzG7z8tNfKHUIqx7bcWG YDUBC/WK35v9SyACrVcgEaYLfyEV8WvC/ye4/9jFYZQjnugHeFcSN3Xo/o7F5+mhFvRDjylmXpA PAxiSLA4qUk+jWl15vjDAKhK9u0BoR9vnyqFVtjUFDMFP5b5Z0dNKt2/CpFbW/DJbBZI6Wy/9b4 5dV/xpVeszlKvw2mLJQ23T7JbNWTuV1NBgn6vFFEI691+bxyu8wR5QF9qJ6H74gK1bVyFLiB42G 4ElgBiz6dHXpbuU1rpk92syTfnO1sQLVlQ6WMTm/6T+njbW+mUSdn1CrC2znip9o/pt8KosJ2Ag == X-Received: by 2002:a17:907:e014:20b0:c1c:2b61:b9da with SMTP id a640c23a62f3a-c1c50bf22ecmr157944266b.34.1784849354297; Thu, 23 Jul 2026 16:29:14 -0700 (PDT) Received: from beelink.. ([186.247.163.143]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-c1c32a78927sm297178566b.12.2026.07.23.16.29.09 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 16:29:13 -0700 (PDT) From: Aldo Ariel Panzardo To: linux-bluetooth@vger.kernel.org Cc: marcel@holtmann.org, luiz.dentz@gmail.com, pav@iki.fi, linux-kernel@vger.kernel.org, Aldo Ariel Panzardo , stable@vger.kernel.org Subject: [PATCH v2] Bluetooth: SCO: give the socket its own sco_conn reference Date: Thu, 23 Jul 2026 20:29:02 -0300 Message-ID: <20260723232902.792805-1-qwe.aldo@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 =3D sco_conn_hold_unless_zero(conn); ... sk =3D 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 =3D=3D NULL, its own put drops the cou= nt 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 =3D 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_con= n") Cc: stable@vger.kernel.org Signed-off-by: Aldo Ariel Panzardo --- 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 =20 BT_DBG("conn %p", conn); =20 - if (conn->sk) - sco_pi(conn->sk)->conn =3D NULL; - if (conn->hcon) { conn->hcon->sco_data =3D 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); =20 - if (!sk) { - sco_conn_put(conn); + if (!sk) return; - } =20 /* Kill socket */ lock_sock(sk); @@ -283,7 +278,7 @@ static void __sco_chan_add(struct sco_co { BT_DBG("conn %p", conn); =20 - sco_pi(sk)->conn =3D conn; + sco_pi(sk)->conn =3D sco_conn_hold(conn); conn->sk =3D sk; =20 if (parent) @@ -366,12 +361,14 @@ static int sco_connect(struct sock *sk) */ if (sk->sk_state !=3D BT_OPEN && sk->sk_state !=3D BT_BOUND) { release_sock(sk); + sco_conn_put(conn); hci_conn_drop(hcon); err =3D -EBADFD; goto unlock; } =20 err =3D 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); =20 - sco_conn_hold(conn); hci_conn_hold(conn->hcon); __sco_chan_add(conn, sk, parent); =20 --=20 2.43.0