CVE-2026-99011: Bluetooth: SCO: serialise sco_conn lifetime against sco_recv_scodata()
In the Linux kernel, the following vulnerability has been resolved:
Bluetooth: SCO: serialise scoconn lifetime against scorecvscodata()
scorecvscodata() upgrades the weak hcon->scodata back-pointer to a strong reference under hdev->lock:
hcidevlock(hdev); hcon = hciconnhashlookuphandle(hdev, handle); ... conn = scoconnholdunlesszero(hcon->scodata); hcidevunlock(hdev);
but the pointer is cleared from the other side without that lock. When the last scoconn reference is dropped, scoconnfree() ran
conn->hcon->scodata = NULL;
with no hdev->lock held, so the RX path could read hcon->scodata and call krefgetunlesszero() on an scoconn that was concurrently freed:
BUG: KASAN: slab-use-after-free in scoconnholdunlesszero+0xbe/0x160 Write of size 4 by task kworker/u17:0 Workqueue: hci0 hcirxwork Call Trace: scoconnholdunlesszero+0xbe/0x160 scorecvscodata+0x13f/0x490 hcirxwork+0x3af/0x730
krefgetunlesszero() only guards against a zero refcount, not against the backing memory already being freed.
Give hcon->scodata an actual reference on the scoconn it points to, so the object cannot be freed while the pointer is still installed, and only clear and drop it from scoconndel(), which runs under hdev->lock (its callers, scoconnectcfm() and scodisconncfm(), hold it). The reader in scorecvscodata() 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.
scoconnadd() no longer hands its krefinit() reference to the caller as the connection's only reference; that initial reference is the one owned by hcon->scodata, and callers get their own via scoconnhold().
Because the scoconn can now outlive the socket (the association keeps it alive until the link goes down), the hciconn can no longer be dropped from scoconnfree() without regressing socket close: closing a connected SCO socket must still tear the link down. Move hciconn ownership to the socket instead: scochanadd() takes an hciconn reference and it is released from scochandel() and scosockdestruct(), exactly once. The scoconn no longer owns an hciconn reference, so scoconnadd() stops consuming one and scoconnect() drops the hciconnectsco() reference on every path; scoconnectcfm() no longer needs its hciconnhold() dance.