Skip to contentSkip to conversation

plugins/admin: make AdminPolicy state per-adapter

PATCH Superseded Frédéric Danis 3 participants 9 messages Jul 2 – Jul 15 Bluetooth drivers

v1 of 3 Superseded Latest is v3 →

lore
Share & tools
lore
#0 lore raw

PATCH 1/7 plugins/admin: make AdminPolicy state per-adapter

Fix AdminPolicy D-Bus updates being emitted on the wrong adapter path by removing the single global policy context and moving to per-adapter policy objects.

Changes include:
- track policy contexts in a policy queue keyed by adapter pointer
- keep per-adapter device lists inside each policy context
- emit ServiceAllowList changes using the callback's adapter context
- scope device affected updates to the current adapter only
- clean up probe/remove lifecycle so adapters are registered and torn down independently
- remove remaining global policy_data/devices coupling

Assisted-by GPT:GPT-5.3-Codex
---
 plugins/admin.c | 130 ++++++++++++++++++++++++++++++++++++------------
 1 file changed, 99 insertions(+), 31 deletions(-)
plugins/admin.c +99 -31
index 76c65d068..6c5c8d97f 100644
@@ -39,21 +39,45 @@
 #define BTD_DEVICE_INTERFACE		"org.bluez.Device1"
 
 static DBusConnection *dbus_conn;
-static struct queue *devices; /* List of struct device_data objects */
+static struct queue *policies; /* List of struct btd_admin_policy objects */
 
-/* |policy_data| has the same life cycle as btd_adapter */
-static struct btd_admin_policy {
+/* One policy context per adapter */
+struct btd_admin_policy {
 	struct btd_adapter *adapter;
 	uint16_t adapter_id;
 	struct queue *service_allowlist;
-} *policy_data = NULL;
+	struct queue *devices;
+};
 
 struct device_data {
 	struct btd_device *device;
 	char *path;
 	bool affected;
+	struct btd_admin_policy *policy;
 };
 
+static void free_device_data(void *data);
+static void unregister_device_data(void *data, void *user_data);
+
+static bool policy_match_adapter(const void *data, const void *match_data)
+{
+	const struct btd_admin_policy *policy = data;
+	const struct btd_adapter *adapter = match_data;
+
+	if (!policy)
+		return false;
+
+	return policy->adapter == adapter;
+}
+
+static struct btd_admin_policy *admin_policy_find(struct btd_adapter *adapter)
+{
+	if (!policies)
+		return NULL;
+
+	return queue_find(policies, policy_match_adapter, adapter);
+}
+
 static struct btd_admin_policy *admin_policy_new(struct btd_adapter *adapter)
 {
 	struct btd_admin_policy *admin_policy = NULL;
@@ -68,6 +92,16 @@ static struct btd_admin_policy *admin_policy_new(struct btd_adapter *adapter)
 	admin_policy->adapter = adapter;
 	admin_policy->adapter_id = btd_adapter_get_index(adapter);
 	admin_policy->service_allowlist = queue_new();
+	admin_policy->devices = queue_new();
+
+	if (!admin_policy->service_allowlist || !admin_policy->devices) {
+		queue_destroy(admin_policy->service_allowlist, free);
+		queue_destroy(admin_policy->devices, NULL);
+		g_free(admin_policy);
+		btd_error(btd_adapter_get_index(adapter),
+				"Failed to allocate queues for admin_policy");
+		return NULL;
+	}
 
 	return admin_policy;
 }
@@ -82,6 +116,7 @@ static void admin_policy_free(void *data)
 	struct btd_admin_policy *admin_policy = data;
 
 	free_service_allowlist(admin_policy->service_allowlist);
+	queue_destroy(admin_policy->devices, free_device_data);
 	g_free(admin_policy);
 }
 
@@ -89,6 +124,8 @@ static void admin_policy_destroy(struct btd_admin_policy *admin_policy)
 {
 	const char *path = adapter_get_path(admin_policy->adapter);
 
+	queue_foreach(admin_policy->devices, unregister_device_data, NULL);
+
 	g_dbus_unregister_interface(dbus_conn, path,
 						ADMIN_POLICY_SET_INTERFACE);
 	g_dbus_unregister_interface(dbus_conn, path,
@@ -346,7 +383,7 @@ static void load_policy_settings(struct btd_admin_policy *admin_policy)
 			btd_adapter_get_storage_dir(admin_policy->adapter));
 
 	if (stat(filename, &st) < 0)
-		store_policy_settings(policy_data);
+		store_policy_settings(admin_policy);
 
 	key_file = g_key_file_new();
 
@@ -387,11 +424,11 @@ static DBusMessage *set_service_allowlist(DBusConnection *conn,
 	}
 
 	g_dbus_emit_property_changed(dbus_conn,
-					adapter_get_path(policy_data->adapter),
+					adapter_get_path(admin_policy->adapter),
 					ADMIN_POLICY_STATUS_INTERFACE,
 					"ServiceAllowList");
 
-	queue_foreach(devices, update_device_affected, NULL);
+	queue_foreach(admin_policy->devices, update_device_affected, NULL);
 
 	return dbus_message_new_method_return(msg);
 }
@@ -488,66 +525,86 @@ static void remove_device_data(void *data)
 
 	DBG("device_data for %s removing", device_data->path);
 
-	queue_remove(devices, device_data);
+	if (device_data->policy)
+		queue_remove(device_data->policy->devices, device_data);
+
 	free_device_data(device_data);
 }
 
 static int admin_policy_adapter_probe(struct btd_adapter *adapter)
 {
+	struct btd_admin_policy *policy;
 	const char *adapter_path;
 
-	if (!devices)
-		devices = queue_new();
+	if (!policies)
+		policies = queue_new();
 
-	if (policy_data) {
-		btd_warn(policy_data->adapter_id,
+	if (!policies)
+		return -ENOMEM;
+
+	if (admin_policy_find(adapter)) {
+		btd_warn(btd_adapter_get_index(adapter),
 						"Policy data already exists");
-		policy_data = NULL;
+		return -EALREADY;
 	}
 
-	policy_data = admin_policy_new(adapter);
-	if (!policy_data)
+	policy = admin_policy_new(adapter);
+	if (!policy)
 		return -ENOMEM;
 
-	load_policy_settings(policy_data);
+	load_policy_settings(policy);
 	adapter_path = adapter_get_path(adapter);
 
 	if (!g_dbus_register_interface(dbus_conn, adapter_path,
 					ADMIN_POLICY_SET_INTERFACE,
 					admin_policy_adapter_methods, NULL,
-					NULL, policy_data, NULL)) {
-		btd_error(policy_data->adapter_id,
+					NULL, policy, NULL)) {
+		btd_error(policy->adapter_id,
 			"Admin Policy Set interface init failed on path %s",
 								adapter_path);
+		admin_policy_free(policy);
 		return -EINVAL;
 	}
 
-	btd_info(policy_data->adapter_id,
+	btd_info(policy->adapter_id,
 				"Admin Policy Set interface registered");
 
 	if (!g_dbus_register_interface(dbus_conn, adapter_path,
 					ADMIN_POLICY_STATUS_INTERFACE,
 					NULL, NULL,
 					admin_policy_adapter_properties,
-					policy_data, NULL)) {
-		btd_error(policy_data->adapter_id,
+					policy, NULL)) {
+		btd_error(policy->adapter_id,
 			"Admin Policy Status interface init failed on path %s",
 								adapter_path);
+		g_dbus_unregister_interface(dbus_conn, adapter_path,
+						ADMIN_POLICY_SET_INTERFACE);
+		admin_policy_free(policy);
 		return -EINVAL;
 	}
 
-	btd_info(policy_data->adapter_id,
+	btd_info(policy->adapter_id,
 				"Admin Policy Status interface registered");
 
+	queue_push_tail(policies, policy);
+
 	return 0;
 }
 
 static void admin_policy_device_added(struct btd_adapter *adapter,
 						struct btd_device *device)
 {
+	struct btd_admin_policy *policy;
 	struct device_data *data;
 
-	if (queue_find(devices, device_data_match, device))
+	policy = admin_policy_find(adapter);
+	if (!policy) {
+		btd_warn(btd_adapter_get_index(adapter),
+				"Policy data not found for adapter");
+		return;
+	}
+
+	if (queue_find(policy->devices, device_data_match, device))
 		return;
 
 	data = g_new0(struct device_data, 1);
@@ -560,6 +617,7 @@ static void admin_policy_device_added(struct btd_adapter *adapter,
 	data->device = device;
 	data->path = g_strdup(device_get_path(device));
 	data->affected = !btd_device_all_services_allowed(data->device);
+	data->policy = policy;
 
 	if (!g_dbus_register_interface(dbus_conn, data->path,
 					ADMIN_POLICY_STATUS_INTERFACE,
@@ -573,7 +631,7 @@ static void admin_policy_device_added(struct btd_adapter *adapter,
 		return;
 	}
 
-	queue_push_tail(devices, data);
+	queue_push_tail(policy->devices, data);
 
 	DBG("device_data for %s added", data->path);
 }
@@ -589,9 +647,14 @@ static void unregister_device_data(void *data, void *user_data)
 static void admin_policy_device_removed(struct btd_adapter *adapter,
 						struct btd_device *device)
 {
+	struct btd_admin_policy *policy;
 	struct device_data *data;
 
-	data = queue_find(devices, device_data_match, device);
+	policy = admin_policy_find(adapter);
+	if (!policy)
+		return;
+
+	data = queue_find(policy->devices, device_data_match, device);
 
 	if (data)
 		unregister_device_data(data, NULL);
@@ -599,15 +662,20 @@ static void admin_policy_device_removed(struct btd_adapter *adapter,
 
 static void admin_policy_remove(struct btd_adapter *adapter)
 {
+	struct btd_admin_policy *policy;
+
 	DBG("");
 
-	queue_foreach(devices, unregister_device_data, NULL);
-	queue_destroy(devices, g_free);
-	devices = NULL;
+	policy = admin_policy_find(adapter);
+	if (!policy)
+		return;
+
+	queue_remove(policies, policy);
+	admin_policy_destroy(policy);
 
-	if (policy_data) {
-		admin_policy_destroy(policy_data);
-		policy_data = NULL;
+	if (!queue_length(policies)) {
+		queue_destroy(policies, NULL);
+		policies = NULL;
 	}
 }
 
Signature · 1 line
2.43.0
#1 lore raw

PATCH 2/7 client/bluetoothctl: make admin.allow controller-aware

Teach admin.allow to target the selected default controller when no controller is provided, and to accept an explicit [ctrl] argument.

Replace single cached AdminPolicy proxies with per-controller proxy lookup keyed by controller object path, so controller selection changes are respected.

Export controller lookup/default helpers and shared controller completion from main.c for reuse by admin.c.

Assisted-by GPT:GPT-5.3-Codex
---
 client/admin.c | 114 ++++++++++++++++++++++++++++++++++++++-----------
 client/admin.h |   4 ++
 client/main.c  |  25 +++++++++--
 3 files changed, 116 insertions(+), 27 deletions(-)
client/admin.c +90 -24
index dc218ed2c..584d4630b 100644
@@ -16,6 +16,7 @@
 #include <string.h>
 #include <stdlib.h>
 
+#include "bluetooth/bluetooth.h"
 #include "gdbus/gdbus.h"
 #include "src/shared/shell.h"
 
@@ -23,27 +24,79 @@
 #define _GNU_SOURCE
 
 static DBusConnection *dbus_conn;
-static GList *admin_proxies;
-static GDBusProxy *set_proxy;
-static GDBusProxy *status_proxy;
+static GList *set_proxies;
+static GList *status_proxies;
 
 static void admin_menu_pre_run(const struct bt_shell_menu *menu);
 
-static void admin_policy_set_set_proxy(GDBusProxy *proxy)
+static GDBusProxy *admin_policy_find_proxy(GList *proxies,
+						const char *path)
 {
-	set_proxy = proxy;
+	GList *list;
+
+	for (list = g_list_first(proxies); list; list = g_list_next(list)) {
+		GDBusProxy *proxy = list->data;
+
+		if (!strcmp(g_dbus_proxy_get_path(proxy), path))
+			return proxy;
+	}
+
+	return NULL;
+}
+
+static GDBusProxy *admin_policy_get_status_proxy(const char *controller_path)
+{
+	if (!controller_path)
+		return NULL;
+
+	return admin_policy_find_proxy(status_proxies, controller_path);
+}
+
+static GDBusProxy *admin_policy_get_set_proxy(const char *controller_path)
+{
+	if (!controller_path)
+		return NULL;
+
+	return admin_policy_find_proxy(set_proxies, controller_path);
 }
 
-static void admin_policy_set_status_proxy(GDBusProxy *proxy)
+static GDBusProxy *admin_policy_get_controller(int argc, char *argv[],
+						int *arg_index)
 {
-	status_proxy = proxy;
+	GDBusProxy *controller;
+
+	*arg_index = 1;
+
+	if (argc > 1 && strlen(argv[1])) {
+		controller = bluetoothctl_find_controller(argv[1]);
+		if (controller) {
+			*arg_index = 2;
+			return controller;
+		}
+
+		if (bachk(argv[1]) == 0) {
+			bt_shell_printf("Controller %s not available\n",
+								argv[1]);
+			return NULL;
+		}
+	}
+
+	controller = bluetoothctl_get_default_controller();
+	if (controller)
+		return controller;
+
+	bt_shell_printf("No default controller available\n");
+	return NULL;
 }
 
-static void admin_policy_read_service_allowlist(DBusConnection *dbus_conn)
+static void admin_policy_read_service_allowlist(GDBusProxy *controller)
 {
 	DBusMessageIter iter, subiter;
+	GDBusProxy *status_proxy;
 	char *uuid = NULL;
+	const char *controller_path = g_dbus_proxy_get_path(controller);
 
+	status_proxy = admin_policy_get_status_proxy(controller_path);
 	if (!status_proxy || !g_dbus_proxy_get_property(status_proxy,
 						"ServiceAllowList", &iter)) {
 		bt_shell_printf("Failed to get property\n");
@@ -106,10 +159,14 @@ static void set_service_reply(DBusMessage *message, void *user_data)
 	return bt_shell_noninteractive_quit(EXIT_FAILURE);
 }
 
-static void admin_policy_set_service_allowlist(int argc, char *argv[])
+static void admin_policy_set_service_allowlist(GDBusProxy *controller,
+						int argc, char *argv[])
 {
 	struct uuid_list_data data;
+	GDBusProxy *set_proxy;
+	const char *controller_path = g_dbus_proxy_get_path(controller);
 
+	set_proxy = admin_policy_get_set_proxy(controller_path);
 	if (!set_proxy) {
 		bt_shell_printf("Set proxy not ready\n");
 		return bt_shell_noninteractive_quit(EXIT_FAILURE);
@@ -128,15 +185,23 @@ static void admin_policy_set_service_allowlist(int argc, char *argv[])
 
 static void cmd_admin_allow(int argc, char *argv[])
 {
-	if (argc <= 1) {
-		admin_policy_read_service_allowlist(dbus_conn);
+	GDBusProxy *controller;
+	int arg_index;
+
+	controller = admin_policy_get_controller(argc, argv, &arg_index);
+	if (!controller)
+		return bt_shell_noninteractive_quit(EXIT_FAILURE);
+
+	if (argc <= arg_index) {
+		admin_policy_read_service_allowlist(controller);
 		return;
 	}
 
-	if (strcmp(argv[1], "clear") == 0)
-		argc--;
+	if (strcmp(argv[arg_index], "clear") == 0)
+		arg_index++;
 
-	admin_policy_set_service_allowlist(argc - 1, argv + 1);
+	admin_policy_set_service_allowlist(controller, argc - arg_index,
+						argv + arg_index);
 }
 
 static const struct bt_shell_menu admin_menu = {
@@ -144,15 +209,15 @@ static const struct bt_shell_menu admin_menu = {
 	.desc = "Admin Policy Submenu",
 	.pre_run = admin_menu_pre_run,
 	.entries = {
-	{ "allow", "[clear/uuid1 uuid2 ...]", cmd_admin_allow,
-				"Allow service UUIDs and block rest of them"},
+	{ "allow", "[ctrl] [clear/uuid1 uuid2 ...]", cmd_admin_allow,
+				"Allow service UUIDs and block rest of them",
+				bluetoothctl_controller_generator},
 	{} },
 };
 
 static void admin_policy_status_added(GDBusProxy *proxy)
 {
-	admin_proxies = g_list_append(admin_proxies, proxy);
-	admin_policy_set_status_proxy(proxy);
+	status_proxies = g_list_append(status_proxies, proxy);
 }
 
 static void proxy_added(GDBusProxy *proxy, void *user_data)
@@ -162,15 +227,14 @@ static void proxy_added(GDBusProxy *proxy, void *user_data)
 	interface = g_dbus_proxy_get_interface(proxy);
 
 	if (!strcmp(interface, "org.bluez.AdminPolicySet1"))
-		admin_policy_set_set_proxy(proxy);
+		set_proxies = g_list_append(set_proxies, proxy);
 	else if (!strcmp(interface, "org.bluez.AdminPolicyStatus1"))
 		admin_policy_status_added(proxy);
 }
 
 static void admin_policy_status_removed(GDBusProxy *proxy)
 {
-	admin_proxies = g_list_remove(admin_proxies, proxy);
-	admin_policy_set_status_proxy(NULL);
+	status_proxies = g_list_remove(status_proxies, proxy);
 }
 
 static void proxy_removed(GDBusProxy *proxy, void *user_data)
@@ -180,7 +244,7 @@ static void proxy_removed(GDBusProxy *proxy, void *user_data)
 	interface = g_dbus_proxy_get_interface(proxy);
 
 	if (!strcmp(interface, "org.bluez.AdminPolicySet1"))
-		admin_policy_set_set_proxy(NULL);
+		set_proxies = g_list_remove(set_proxies, proxy);
 	else if (!strcmp(interface, "org.bluez.AdminPolicyStatus1"))
 		admin_policy_status_removed(proxy);
 }
@@ -189,8 +253,10 @@ static GDBusClient *client;
 
 static void disconnect_handler(DBusConnection *connection, void *user_data)
 {
-	g_list_free_full(admin_proxies, NULL);
-	admin_proxies = NULL;
+	g_list_free_full(set_proxies, NULL);
+	set_proxies = NULL;
+	g_list_free_full(status_proxies, NULL);
+	status_proxies = NULL;
 }
 
 void admin_add_submenu(void)
client/admin.h +4 -0
index 0047770dc..be58fb2fa 100644
@@ -10,3 +10,7 @@
 
 void admin_add_submenu(void);
 void admin_remove_submenu(void);
+
+GDBusProxy *bluetoothctl_get_default_controller(void);
+GDBusProxy *bluetoothctl_find_controller(const char *address);
+char *bluetoothctl_controller_generator(const char *text, int state);
client/main.c +22 -3
index 9d9ac8b14..5e9f2eeb5 100644
@@ -881,6 +881,25 @@ static struct adapter *find_ctrl_by_address(GList *source, const char *address)
 	return NULL;
 }
 
+GDBusProxy *bluetoothctl_get_default_controller(void)
+{
+	if (!default_ctrl)
+		return NULL;
+
+	return default_ctrl->proxy;
+}
+
+GDBusProxy *bluetoothctl_find_controller(const char *address)
+{
+	struct adapter *adapter;
+
+	adapter = find_ctrl_by_address(ctrl_list, address);
+	if (!adapter)
+		return NULL;
+
+	return adapter->proxy;
+}
+
 static GDBusProxy *find_proxies_by_iface(GList *source, const char *path,
 							const char *iface)
 {
@@ -2788,7 +2807,7 @@ static char *generic_generator(const char *text, int state,
 	return NULL;
 }
 
-static char *ctrl_generator(const char *text, int state)
+char *bluetoothctl_controller_generator(const char *text, int state)
 {
 	static int index = 0;
 	static int len = 0;
@@ -3876,9 +3895,9 @@ static const struct bt_shell_menu main_menu = {
 	.entries = {
 	{ "list",         NULL,       cmd_list, "List available controllers" },
 	{ "show",         "[ctrl]",   cmd_show, "Controller information",
-							ctrl_generator },
+					bluetoothctl_controller_generator },
 	{ "select",       "<ctrl>",   cmd_select, "Select default controller",
-							ctrl_generator },
+					bluetoothctl_controller_generator },
 	{ "devices",      "[Paired/Bonded/Trusted/Connected]", cmd_devices,
 					"List available devices, with an "
 					"optional property as the filter" },
Signature · 1 line
2.43.0
#2 lore raw

PATCH 3/7 doc: document admin.allow optional controller argument

In reply to Frédéric Danis

Update bluetoothctl usage to 'admin.allow [ctrl] [clear/uuid1 uuid2 ...]' and add explicit controller examples.

Refresh AdminPolicySet examples to include the controller-qualified admin.allow form.

Assisted-by GPT:GPT-5.3-Codex
---
 doc/bluetoothctl-admin.rst       | 13 +++++++++++--
 doc/org.bluez.AdminPolicySet.rst |  1 +
 2 files changed, 12 insertions(+), 2 deletions(-)
doc/bluetoothctl-admin.rst +11 -2
index d4446ce95..ce15b8728 100644
@@ -9,7 +9,7 @@ Admin Policy Submenu
 :Version: BlueZ
 :Copyright: Free use of this software is granted under the terms of the GNU
             Lesser General Public Licenses (LGPL).
-:Date: November 2022
+:Date: June 2026
 :Manual section: 1
 :Manual group: Linux System Administration
 
@@ -30,15 +30,22 @@ allow
 
 Allow service UUIDs and block rest of them.
 
-:Usage: **> allow [clear/uuid1 uuid2 ...]**
+:Usage: **> allow [ctrl] [clear/uuid1 uuid2 ...]**
+:[ctrl]: Bluetooth controller address (optional; defaults to the selected controller)
 :Uses: **org.bluez.AdminPolicySet(5)** property **ServiceAllowList**
 :[clear/uuid1 uuid2 ...]: List of service UUIDs to allow, or "clear" to remove all restrictions
 :Example Get current allowed UUIDs list:
 	| **> allow**
+:Example Get current allowed UUIDs list for a specific controller:
+	| **> allow 00:11:22:33:44:55**
 :Example Allow Serial Port Profile only:
 	| **> allow 0x1101**
+:Example Allow Serial Port Profile on a specific controller:
+	| **> allow 00:11:22:33:44:55 0x1101**
 :Example Allow Serial Port Profile and LAN Access Profile:
 	| **> allow 0x1101 0x1102**
+:Example Allow Serial Port Profile and LAN Access Profile on a specific controller:
+	| **> allow 00:11:22:33:44:55 0x1101 0x1102**
 :Example Allow Serial Port Profile, LAN Access Profile, and Dialup Networking Profile:
 	| **> allow 0x1101 0x1102 0x1103**
 :Example Allow Advanced Audio Distribution Profile only:
@@ -51,6 +58,8 @@ Allow service UUIDs and block rest of them.
 	| **> allow 00001101-0000-1000-8000-00805f9b34fb 00001102-0000-1000-8000-00805f9b34fb**
 :Example Remove all UUID restrictions:
 	| **> allow clear**
+:Example Remove all UUID restrictions on a specific controller:
+	| **> allow 00:11:22:33:44:55 clear**
 :Example Allow SPP, LAP, and DUN using mixed UUID formats:
 	| **> allow 0x1101 00001102-0000-1000-8000-00805f9b34fb 0x1103**
 
doc/org.bluez.AdminPolicySet.rst +1 -0
index db132b7de..29fe3bdf7 100644
@@ -55,3 +55,4 @@ Possible errors:
 Examples:
 
 :bluetoothctl: > admin.allow [clear/uuid1 uuid2 ...]
+:bluetoothctl: > admin.allow [ctrl] [clear/uuid1 uuid2 ...]
Signature · 1 line
2.43.0
#3 lore raw

PATCH 4/7 src/adapter: enforce allowlist for local services

In reply to Frédéric Danis

Apply admin allowlist to adapter/server service startup and registration, and reapply policy dynamically when allowlist changes.

- Gate adapter profile probe by allowlist-derived UUID policy
- Reapply active adapter profiles on allowlist updates (stop disallowed, start newly allowed)
- Block SDP service registration when UUID is not allowed
- Reapply existing local SDP registrations at runtime by removing services that become disallowed

Assisted-by GPT:GPT-5.3-Codex
---
 src/adapter.c | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/adapter.h |   1 +
 2 files changed, 140 insertions(+)
src/adapter.c +139 -0
index 538f63e0a..5a293b3f3 100644
@@ -1346,6 +1346,7 @@ done:
 
 int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec)
 {
+	char *svc_uuid;
 	int ret;
 
 	/*
@@ -1357,6 +1358,18 @@ int adapter_service_add(struct btd_adapter *adapter, sdp_record_t *rec)
 
 	DBG("%s", adapter->path);
 
+	svc_uuid = bt_uuid2string(&rec->svclass);
+	if (!svc_uuid)
+		return -ENOMEM;
+
+	if (!btd_adapter_is_uuid_allowed(adapter, svc_uuid)) {
+		DBG("Service %s blocked by admin allowlist", svc_uuid);
+		free(svc_uuid);
+		return -EPERM;
+	}
+
+	free(svc_uuid);
+
 	ret = add_record_to_server(&adapter->bdaddr, rec);
 	if (ret < 0)
 		return ret;
@@ -5201,6 +5214,70 @@ static void load_drivers(struct btd_adapter *adapter)
 		probe_driver(adapter, l->data);
 }
 
+struct profile_allowlist_map {
+	const char *name;
+	const char *uuid;
+	bool use_remote_uuid;
+};
+
+/*
+ * Adapter server policy UUID defaults to local_uuid when available.
+ * Profiles listed below are exceptions where adapter-side server behavior
+ * needs a different UUID source (e.g. A2DP source/sink role inversion) or
+ * where server policy is derived from the profile remote_uuid.
+ */
+static const struct profile_allowlist_map profile_allowlist_map[] = {
+	{ "a2dp-source", A2DP_SINK_UUID, false },
+	{ "a2dp-sink", A2DP_SOURCE_UUID, false },
+	{ "audio-avrcp-target", NULL, true },
+	{ "avrcp-controller", NULL, true },
+	{ "vcp", NULL, true },
+	{ "micp", NULL, true },
+	{ "ccp", NULL, true },
+	{ "gmap", NULL, true },
+	{ "tmap", NULL, true },
+	{ "bass", NULL, true },
+	{ "bap", NULL, true },
+	{ "mcp-gmcs", NULL, true },
+};
+
+static const char *profile_allowlist_uuid(const struct btd_profile *profile)
+{
+	size_t i;
+
+	if (profile->local_uuid)
+		return profile->local_uuid;
+
+	if (!profile->name)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(profile_allowlist_map); i++) {
+		const struct profile_allowlist_map *entry =
+						&profile_allowlist_map[i];
+
+		if (strcmp(profile->name, entry->name))
+			continue;
+
+		if (entry->use_remote_uuid)
+			return profile->remote_uuid;
+
+		return entry->uuid;
+	}
+
+	return NULL;
+}
+
+static bool adapter_profile_is_allowed(struct btd_adapter *adapter,
+					const struct btd_profile *profile)
+{
+	const char *uuid = profile_allowlist_uuid(profile);
+
+	if (!uuid)
+		return true;
+
+	return btd_adapter_is_uuid_allowed(adapter, uuid);
+}
+
 static void probe_profile(struct btd_profile *profile, void *data)
 {
 	struct btd_adapter *adapter = data;
@@ -5209,6 +5286,11 @@ static void probe_profile(struct btd_profile *profile, void *data)
 	if (profile->adapter_probe == NULL)
 		return;
 
+	if (!adapter_profile_is_allowed(adapter, profile)) {
+		DBG("%s blocked by admin allowlist", profile->name);
+		return;
+	}
+
 	err = profile->adapter_probe(profile, adapter);
 	if (err < 0) {
 		btd_error(adapter->dev_id, "%s: %s (%d)", profile->name,
@@ -5219,6 +5301,63 @@ static void probe_profile(struct btd_profile *profile, void *data)
 	adapter->profiles = g_slist_prepend(adapter->profiles, profile);
 }
 
+static void reapply_profile(struct btd_profile *profile, void *data)
+{
+	struct btd_adapter *adapter = data;
+	bool active;
+
+	if (profile->adapter_probe == NULL)
+		return;
+
+	active = g_slist_find(adapter->profiles, profile) != NULL;
+
+	if (adapter_profile_is_allowed(adapter, profile)) {
+		if (!active)
+			probe_profile(profile, adapter);
+		return;
+	}
+
+	if (!active)
+		return;
+
+	adapter->profiles = g_slist_remove(adapter->profiles, profile);
+
+	if (profile->adapter_remove)
+		profile->adapter_remove(profile, adapter);
+}
+
+static void reapply_adapter_services(struct btd_adapter *adapter)
+{
+	sdp_list_t *l;
+
+	for (l = adapter->services; l != NULL;) {
+		sdp_list_t *next = l->next;
+		sdp_record_t *rec = l->data;
+		char *svc_uuid;
+
+		svc_uuid = bt_uuid2string(&rec->svclass);
+		if (!svc_uuid) {
+			l = next;
+			continue;
+		}
+
+		if (!btd_adapter_is_uuid_allowed(adapter, svc_uuid))
+			adapter_service_remove(adapter, rec->handle);
+
+		free(svc_uuid);
+		l = next;
+	}
+}
+
+void btd_adapter_reapply_allowed_uuids(struct btd_adapter *adapter)
+{
+	if (!adapter || !adapter->initialized)
+		return;
+
+	btd_profile_foreach(reapply_profile, adapter);
+	reapply_adapter_services(adapter);
+}
+
 void adapter_add_profile(struct btd_adapter *adapter, gpointer p)
 {
 	struct btd_profile *profile = p;
src/adapter.h +1 -0
index 4e07f71ad..cec528c56 100644
@@ -295,6 +295,7 @@ bool btd_adapter_set_allowed_uuids(struct btd_adapter *adapter,
 							struct queue *uuids);
 bool btd_adapter_is_uuid_allowed(struct btd_adapter *adapter,
 							const char *uuid_str);
+void btd_adapter_reapply_allowed_uuids(struct btd_adapter *adapter);
 
 void btd_adapter_load_conn_param(struct btd_adapter *adapter,
 				const bdaddr_t *peer, uint8_t bdaddr_type,
Signature · 1 line
2.43.0
#4 lore raw

PATCH 5/7 plugins/admin: reapply allowlist on policy updates

In reply to Frédéric Danis

Invoke adapter allowlist reapply after SetServiceAllowList updates so runtime state follows policy changes immediately.

Assisted-by GPT:GPT-5.3-Codex
---
 plugins/admin.c | 1 +
 1 file changed, 1 insertion(+)
plugins/admin.c +1 -0
index 6c5c8d97f..07bd0bf3d 100644
@@ -203,6 +203,7 @@ static bool service_allowlist_set(struct btd_admin_policy *admin_policy,
 
 	free_service_allowlist(admin_policy->service_allowlist);
 	admin_policy->service_allowlist = uuid_list;
+	btd_adapter_reapply_allowed_uuids(adapter);
 
 	return true;
 }
Signature · 1 line
2.43.0
#5 lore raw

PATCH 6/7 doc: describe admin allowlist runtime enforcement

In reply to Frédéric Danis

Document that ServiceAllowList now also governs local adapter/server startup and registration, and that allowlist updates are applied immediately on initialized adapters.

Clarify ServiceAllowList status semantics for both remote profile connection policy and local server policy.

Assisted-by GPT:GPT-5.3-Codex
---
 doc/org.bluez.AdminPolicySet.rst    | 11 +++++++++++
 doc/org.bluez.AdminPolicyStatus.rst |  5 +++++
 2 files changed, 16 insertions(+)
doc/org.bluez.AdminPolicySet.rst +11 -0
index 29fe3bdf7..9ac0aa76f 100644
@@ -41,6 +41,17 @@ Sets the service allowlist by specifying service UUIDs.
 When called, **bluetoothd(8)** will block incoming and outgoing connections to
 the service not in UUIDs for all of the clients.
 
+The allowlist also applies to local adapter/server services. When an allowlist
+exists, only adapter/server services whose policy UUID is in UUIDs are started
+or registered.
+
+Updating the allowlist is applied immediately on initialized adapters:
+
+- services that become disallowed are stopped/removed
+- services that become allowed are started/registered
+
+This does not require restarting **bluetoothd(8)** or power-cycling adapters.
+
 Any subsequent calls to this method will supersede any previously set allowlist
 values.  Calling this method with an empty array will allow any service UUIDs to
 be used.
doc/org.bluez.AdminPolicyStatus.rst +5 -0
index 702e020aa..d44ab9361 100644
@@ -43,6 +43,11 @@ array{string} ServiceAllowList [readonly, adapter-only]
 
 Current value of service allow list.
 
+When non-empty, this list controls both:
+
+- remote service connection policy for device profiles
+- local adapter/server service startup and registration policy
+
 bool IsAffectedByPolicy [readonly, device-only]
 ```````````````````````````````````````````````
 
Signature · 1 line
2.43.0
#6 lore raw

PATCH 7/7 profiles/audio: fix UAF on external media service teardown

In reply to Frédéric Danis

Keep media_app endpoint/player queues in sync with object lifetime to avoid stale pointers during proxy removal.

When admin allowlist reapply removes audio services, endpoint/player objects may be destroyed through non-proxy paths first. Later proxy_removed_cb calls queue_remove_if() and matching by path can dereference freed endpoint/player memory.

Fix by:
- adding media_app back-references in media_endpoint/local_player
- unlinking from app queues inside media_endpoint_remove or local_player_remove
- setting ownership when app-registering endpoint/player objects

This prevents heap-use-after-free in match_endpoint_by_path or match_player_by_path during service disconnect.

Assisted-by GPT:GPT-5.3-Codex
---
 profiles/audio/media.c | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
profiles/audio/media.c +16 -0
index 5d9ea2cbc..95f9580b0 100644
@@ -136,12 +136,14 @@ struct media_endpoint {
 	guint			watch;
 	GSList			*requests;
 	struct media_adapter	*adapter;
+	struct media_app	*app;
 	GSList			*transports;
 	struct endpoint_features	features;
 };
 
 struct local_player {
 	struct media_adapter	*adapter;
+	struct media_app	*app;
 	char			*sender;	/* Player DBus bus id */
 	char			*path;		/* Player object path */
 	GHashTable		*settings;	/* Player settings */
@@ -305,6 +307,11 @@ static void media_endpoint_remove(void *data)
 	}
 #endif
 
+	if (endpoint->app) {
+		queue_remove(endpoint->app->endpoints, endpoint);
+		endpoint->app = NULL;
+	}
+
 	info("Endpoint unregistered: sender=%s path=%s", endpoint->sender,
 			endpoint->path);
 
@@ -2076,6 +2083,11 @@ static void local_player_remove(void *data)
 {
 	struct local_player *mp = data;
 
+	if (mp->app) {
+		queue_remove(mp->app->players, mp);
+		mp->app = NULL;
+	}
+
 	info("Player unregistered: sender=%s path=%s", mp->sender, mp->path);
 
 	local_player_destroy(mp);
@@ -3168,6 +3180,8 @@ static void app_register_endpoint(void *data, void *user_data)
 		return;
 	}
 
+	endpoint->app = app;
+
 	queue_push_tail(app->endpoints, endpoint);
 
 	return;
@@ -3196,6 +3210,8 @@ static void app_register_player(void *data, void *user_data)
 	if (!player)
 		return;
 
+	player->app = app;
+
 	if (g_dbus_proxy_get_property(proxy, "PlaybackStatus", &iter)) {
 		if (!set_status(player, &iter))
 			goto fail;
Signature · 1 line
2.43.0
#7 lore raw
In reply to Frédéric Danis

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=1120331

---Test result---

Test Summary:

CheckPatch                    FAIL      2.63 seconds
GitLint                       PASS      1.64 seconds
BuildEll                      PASS      20.98 seconds
BluezMake                     PASS      546.48 seconds
MakeCheck                     PASS      19.10 seconds
MakeDistcheck                 PASS      160.21 seconds
CheckValgrind                 PASS      229.85 seconds
CheckSmatch                   PASS      315.68 seconds
bluezmakeextell               PASS      102.92 seconds
IncrementalBuild              PASS      585.69 seconds
ScanBuild                     PASS      973.73 seconds

Details
##############################
Test: CheckPatch - FAIL
Desc: Run checkpatch.pl script
Output:
[BlueZ,1/7] plugins/admin: make AdminPolicy state per-adapter WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#80:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #80:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658608.patch total: 1 errors, 1 warnings, 259 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/14658608.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

[BlueZ,2/7] client/bluetoothctl: make admin.allow controller-aware WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#81:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #81:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658607.patch total: 1 errors, 1 warnings, 244 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/14658607.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

[BlueZ,3/7] doc: document admin.allow optional controller argument WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#77:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #77:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658606.patch total: 1 errors, 1 warnings, 43 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/14658606.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

[BlueZ,4/7] src/adapter: enforce allowlist for local services WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#80:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #80:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658613.patch total: 1 errors, 1 warnings, 176 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/14658613.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

[BlueZ,5/7] plugins/admin: reapply allowlist on policy updates WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#73:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #73:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658605.patch total: 1 errors, 1 warnings, 7 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/14658605.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

[BlueZ,6/7] doc: describe admin allowlist runtime enforcement WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#77:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #77:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658609.patch total: 1 errors, 1 warnings, 28 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/14658609.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

[BlueZ,7/7] profiles/audio: fix UAF on external media service teardown WARNING:BAD_SIGN_OFF: Non-standard signature: Assisted-by:
#88:

Assisted-by GPT:GPT-5.3-Codex

ERROR:BAD_SIGN_OFF: Unrecognized email address: 'GPT:GPT-5.3-Codex' #88:

Assisted-by GPT:GPT-5.3-Codex

/github/workspace/src/patch/14658610.patch total: 1 errors, 1 warnings, 52 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/14658610.patch has style problems, please review.

NOTE: Ignored message types: COMMIT_MESSAGE COMPLEX_MACRO CONST_STRUCT FILE_PATH_CHANGES MISSING_SIGN_OFF PREFER_PACKED SPDX_LICENSE_TAG SPLIT_STRING SSCANF_TO_KSTRTO

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

https://github.com/bluez/bluez/pull/2262

---
Regards,
Linux Bluetooth

#8 lore raw
In reply to Frédéric Danis
This series was applied to bluetooth/bluez.git (master) by Luiz Augusto von Dentz <[email protected]>:

Hello:

This series was applied to bluetooth/bluez.git (master) by Luiz Augusto von Dentz <[email protected]>:

Quoted 13 lines · On Thu, 2 Jul 2026 10:36:35 +0200 you wrote:

Fix AdminPolicy D-Bus updates being emitted on the wrong adapter path by removing the single global policy context and moving to per-adapter policy objects.

Changes include:
- track policy contexts in a policy queue keyed by adapter pointer
- keep per-adapter device lists inside each policy context
- emit ServiceAllowList changes using the callback's adapter context
- scope device affected updates to the current adapter only
- clean up probe/remove lifecycle so adapters are registered and torn down independently
- remove remaining global policy_data/devices coupling

Quoted 1 line

[...]

Here is the summary with links:
- [BlueZ,1/7] plugins/admin: make AdminPolicy state per-adapter (no matching commit)
- [BlueZ,2/7] client/bluetoothctl: make admin.allow controller-aware (no matching commit)
- [BlueZ,3/7] doc: document admin.allow optional controller argument (no matching commit)
- [BlueZ,4/7] src/adapter: enforce allowlist for local services (no matching commit)
- [BlueZ,5/7] plugins/admin: reapply allowlist on policy updates (no matching commit)
- [BlueZ,6/7] doc: describe admin allowlist runtime enforcement (no matching commit)
- [BlueZ,7/7] profiles/audio: fix UAF on external media service teardown

    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=7482aceece70

You are awesome, thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html