From: "Frédéric Danis" <frederic.danis@collabora.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v3 7/7] profiles/audio: make A2DP admin allowlist enforcement role-safe
Date: Wed, 19 Aug 2026 16:43:37 +0200 [thread overview]
Message-ID: <20260819144337.889893-8-frederic.danis@collabora.com> (raw)
In-Reply-To: <20260819144337.889893-1-frederic.danis@collabora.com>
Unify audio-side allowlist enforcement so A2DP behavior remains correct
across dynamic policy updates, reconnects, and role-specific endpoint
negotiation.
Why:
- Dynamic allowlist reapply could leave stale A2DP role state and dangling
role lists, leading to invalid reuse and instability during subsequent
signaling/SEP handling.
- Policy enforcement happening late (at SetConfiguration time) allowed
blocked roles to remain visible during capability negotiation, which caused
retry/disconnect behavior until session managers were restarted.
What changed:
- Harden A2DP server role removal bookkeeping:
- clear source_enabled/sink_enabled flags on remove
- clear server->sources/server->sinks list heads after free
- unregister shared server only when both roles are disabled
- Enforce admin policy at SEP negotiation boundaries:
- add role-aware helper mapping local SEP type to policy UUID
- reject blocked roles in Get_Capability path
- reject blocked roles early in Set_Configuration path
- Keep endpoint registration resilient when partial allowlists block specific
A2DP records, so allowed endpoints continue to register.
Result:
- A2DP policy decisions are consistent with local role semantics.
- Blocked roles are filtered earlier and more predictably.
- Runtime policy transitions are stable, without requiring daemon/session
manager restarts to recover expected reconnect behavior.
Assisted-by: GPT:GPT-5.3-Codex
---
profiles/audio/a2dp.c | 38 ++++++++++++++++++++++++++++++++++----
profiles/audio/media.c | 7 +++++++
2 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index a4ba1dacf..0d4b8cc32 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -811,6 +811,20 @@ static void reverse_discover(struct avdtp *session, GSList *seps, int err,
DBG("err %d", err);
}
+static bool a2dp_sep_policy_allowed(struct avdtp *session,
+ const struct a2dp_sep *sep)
+{
+ struct btd_adapter *adapter = avdtp_get_adapter(session);
+ const char *uuid;
+
+ if (sep->type == AVDTP_SEP_TYPE_SOURCE)
+ uuid = A2DP_SOURCE_UUID;
+ else
+ uuid = A2DP_SINK_UUID;
+
+ return btd_adapter_is_uuid_allowed(adapter, uuid);
+}
+
static gboolean endpoint_setconf_ind(struct avdtp *session,
struct avdtp_local_sep *sep,
struct avdtp_stream *stream,
@@ -827,6 +841,9 @@ static gboolean endpoint_setconf_ind(struct avdtp *session,
else
DBG("Source %p: Set_Configuration_Ind", sep);
+ if (!a2dp_sep_policy_allowed(session, a2dp_sep))
+ return FALSE;
+
a2dp_stream = a2dp_stream_get(a2dp_sep, session);
if (!a2dp_stream)
return FALSE;
@@ -905,6 +922,11 @@ static gboolean endpoint_getcap_ind(struct avdtp *session,
else
DBG("Source %p: Get_Capability_Ind", sep);
+ if (!a2dp_sep_policy_allowed(session, a2dp_sep)) {
+ *err = AVDTP_BAD_ACP_SEID;
+ return FALSE;
+ }
+
*caps = NULL;
media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT,
@@ -2858,6 +2880,7 @@ struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type,
GSList **l;
uint32_t *record_id;
sdp_record_t *record;
+ int ret;
server = find_server(servers, adapter);
if (server == NULL) {
@@ -2918,12 +2941,13 @@ struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type,
return NULL;
}
- if (adapter_service_add(server->adapter, record) < 0) {
+ ret = adapter_service_add(server->adapter, record);
+ if (ret < 0) {
error("Unable to register A2DP service record");
sdp_record_free(record);
a2dp_unregister_sep(sep);
if (err)
- *err = -EINVAL;
+ *err = ret;
return NULL;
}
@@ -3687,8 +3711,11 @@ static void a2dp_source_server_remove(struct btd_profile *p,
if (!server)
return;
+ server->source_enabled = FALSE;
+
g_slist_free_full(server->sources,
(GDestroyNotify) a2dp_unregister_sep);
+ server->sources = NULL;
if (server->source_record_id) {
adapter_service_remove(server->adapter,
@@ -3696,7 +3723,7 @@ static void a2dp_source_server_remove(struct btd_profile *p,
server->source_record_id = 0;
}
- if (server->sink_record_id)
+ if (server->sink_enabled)
return;
a2dp_server_unregister(server);
@@ -3734,14 +3761,17 @@ static void a2dp_sink_server_remove(struct btd_profile *p,
if (!server)
return;
+ server->sink_enabled = FALSE;
+
g_slist_free_full(server->sinks, (GDestroyNotify) a2dp_unregister_sep);
+ server->sinks = NULL;
if (server->sink_record_id) {
adapter_service_remove(server->adapter, server->sink_record_id);
server->sink_record_id = 0;
}
- if (server->source_record_id)
+ if (server->source_enabled)
return;
a2dp_server_unregister(server);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index 95f9580b0..26a6d7dea 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -3175,6 +3175,13 @@ static void app_register_endpoint(void *data, void *user_data)
metadata, metadata_size,
&features, &app->err);
if (!endpoint) {
+ if (app->err == -EPERM) {
+ info("Skipping endpoint %s:%s (%s) blocked by admin allowlist",
+ app->sender, path, uuid);
+ app->err = 0;
+ return;
+ }
+
error("Unable to register endpoint %s:%s: %s", app->sender,
path, strerror(-app->err));
return;
--
2.43.0
prev parent reply other threads:[~2026-08-19 14:43 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
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 ` Frédéric Danis [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260819144337.889893-8-frederic.danis@collabora.com \
--to=frederic.danis@collabora.com \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is 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.