Linux bluetooth development
 help / color / mirror / Atom feed
* [PATCH BlueZ v3 0/7] plugin/admin: Make allowlist adapter-scoped and enforce at runtime
@ 2026-08-19 14:43 Frédéric Danis
  2026-08-19 14:43 ` [PATCH BlueZ v3 1/7] plugins/admin: make AdminPolicy state per-adapter Frédéric Danis
                   ` (6 more replies)
  0 siblings, 7 replies; 10+ messages in thread
From: Frédéric Danis @ 2026-08-19 14:43 UTC (permalink / raw)
  To: linux-bluetooth

This series tightens AdminPolicy ServiceAllowList handling and makes
its behavior consistent across adapter selection, adapter profile
activation, local SDP service registration, and device service
allowance checks.

The first part removes global AdminPolicy state and makes policy
tracking per-adapter, which fixes D-Bus updates being emitted on the
wrong adapter and avoids cross-adapter coupling.

The second part updates bluetoothctl admin.allow to be adapter-aware:
it now targets the currently selected default adapter instead of a
single global proxy.

The core functional change is runtime enforcement of ServiceAllowList
for local adapter/server services in addition to existing remote-device
policy checks. When policy changes, adapters immediately reapply
allowlist decisions by stopping/removing disallowed services and
starting newly allowed ones, without restarting bluetoothd.

Finally, device-side and A2DP allowlist checks are made role-aware, so
that local Sink/Source role UUIDs are used consistently across
adapter profile probing, device connection filtering, and A2DP SEP
negotiation, fixing role-inverted cases and blocking disallowed roles
earlier (at Get_Capability/Set_Configuration time) instead of relying
on later teardown paths.

Summary of effects:
- AdminPolicy state is correctly scoped per adapter.
- admin.allow targets the currently selected default adapter.
- ServiceAllowList now governs both remote profiles and local
  adapter/server services.
- Allowlist updates are enforced immediately on initialized adapters.
- Device, adapter, and A2DP policy decisions consistently use
  role-aware UUID mapping semantics.
- Blocked A2DP roles are rejected at SEP negotiation time, avoiding
  stale role state and reconnect instability across policy updates.

v1->v2: Add new commit to fix outgoing connection by unifying device-side
 filtering with adapter-side profile-aware allowlist mapping semantics.

v2->v3:
- Drop the "profiles/audio: fix UAF on external media service
  teardown" commit, since it has already been merged upstream
  separately and is no longer part of this series.
- Drop the optional [ctrl] argument from bluetoothctl's admin.allow;
  it now always targets the currently selected default adapter, which
  also removed the need for the separate doc patch documenting that
  argument.
- src/adapter: map both a2dp-source and a2dp-sink profile probing to
  ADVANCED_AUDIO_UUID (0x110d) instead of swapped Sink/Source UUIDs,
  so profile probe gating matches the A2DP profile class while local
  Source/Sink SDP records still get filtered by their own UUIDs.
- device: rework device-side service allowance to use a dedicated
  role-aware helper (service_policy_uuid()) mapping a2dp-sink to the
  A2DP Source UUID and a2dp-source to the A2DP Sink UUID, instead of
  the shared btd_adapter_is_profile_allowed() helper, fixing a false
  block seen during host-initiated A2DP connect attempts.
- Add a new commit making A2DP allowlist enforcement itself role-safe:
  policy is now checked at Get_Capability/Set_Configuration
  negotiation time (rejecting blocked roles early), a2dp.c server
  role bookkeeping was hardened (explicit source_enabled/sink_enabled
  flags, cleared source/sink list heads) to avoid stale role state
  across dynamic reapply, and media.c now treats endpoints skipped by
  admin allowlist as non-fatal instead of logging an error.

Frédéric Danis (7):
  plugins/admin: make AdminPolicy state per-adapter
  client/bluetoothctl: make admin.allow controller-aware
  src/adapter: enforce allowlist for local services
  plugins/admin: reapply allowlist on policy updates
  doc: describe admin allowlist runtime enforcement
  device: unify admin allowlist checks for device services
  profiles/audio: make A2DP admin allowlist enforcement role-safe

 client/admin.c                      |  74 +++++++++++----
 client/admin.h                      |   2 +
 client/main.c                       |   8 ++
 doc/org.bluez.AdminPolicySet.rst    |  11 +++
 doc/org.bluez.AdminPolicyStatus.rst |   5 +
 plugins/admin.c                     | 131 ++++++++++++++++++++------
 profiles/audio/a2dp.c               |  38 +++++++-
 profiles/audio/media.c              |   7 ++
 src/adapter.c                       | 141 ++++++++++++++++++++++++++++
 src/adapter.h                       |   4 +
 src/device.c                        |  35 ++++++-
 11 files changed, 398 insertions(+), 58 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 10+ messages in thread
* [PATCH BlueZ v2 1/8] plugins/admin: make AdminPolicy state per-adapter
@ 2026-07-15  9:01 Frédéric Danis
  2026-07-15 10:16 ` plugin/admin: Make allowlist adapter-scoped and enforce at runtime bluez.test.bot
  0 siblings, 1 reply; 10+ messages in thread
From: Frédéric Danis @ 2026-07-15  9:01 UTC (permalink / raw)
  To: linux-bluetooth

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(-)

diff --git a/plugins/admin.c b/plugins/admin.c
index 76c65d068..6c5c8d97f 100644
--- a/plugins/admin.c
+++ b/plugins/admin.c
@@ -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;
 	}
 }
 
-- 
2.43.0


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

end of thread, other threads:[~2026-08-19 15:45 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-19 14:43 [PATCH BlueZ v3 0/7] plugin/admin: Make allowlist adapter-scoped and enforce at runtime Frédéric Danis
2026-08-19 14:43 ` [PATCH BlueZ v3 1/7] plugins/admin: make AdminPolicy state per-adapter Frédéric Danis
2026-08-19 15:45   ` plugin/admin: Make allowlist adapter-scoped and enforce at runtime bluez.test.bot
2026-08-19 14:43 ` [PATCH BlueZ v3 2/7] client/bluetoothctl: make admin.allow controller-aware Frédéric Danis
2026-08-19 14:43 ` [PATCH BlueZ v3 3/7] src/adapter: enforce allowlist for local services Frédéric Danis
2026-08-19 14:43 ` [PATCH BlueZ v3 4/7] plugins/admin: reapply allowlist on policy updates Frédéric Danis
2026-08-19 14:43 ` [PATCH BlueZ v3 5/7] doc: describe admin allowlist runtime enforcement Frédéric Danis
2026-08-19 14:43 ` [PATCH BlueZ v3 6/7] device: unify admin allowlist checks for device services Frédéric Danis
2026-08-19 14:43 ` [PATCH BlueZ v3 7/7] profiles/audio: make A2DP admin allowlist enforcement role-safe Frédéric Danis
  -- strict thread matches above, loose matches on Subject: below --
2026-07-15  9:01 [PATCH BlueZ v2 1/8] plugins/admin: make AdminPolicy state per-adapter Frédéric Danis
2026-07-15 10:16 ` plugin/admin: Make allowlist adapter-scoped and enforce at runtime bluez.test.bot

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