From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v2 2/3] a2dp: Fix crash on NULL session in auto_config
Date: Wed, 7 Oct 2026 15:52:05 -0400 [thread overview]
Message-ID: <20261007195206.350586-2-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20261007195206.350586-1-luiz.dentz@gmail.com>
From: Eduardo Alves <eduardoalves8006@gmail.com>
If the AVDTP channel is disconnected while bluetoothd is still waiting
for the MediaEndpoint1 to reply to SetConfiguration, channel_free()
sets setup->session to NULL but the setup stays alive since the pending
endpoint request holds a reference to it. Once the endpoint replies, or
the call times out and the request is canceled, endpoint_setconf_cb()
calls auto_config() which passes the NULL session to avdtp_get_device()
and then dereferences the resulting NULL device:
bluetoothd[820]: profiles/audio/media.c:endpoint_reply() Endpoint
replied with an error: org.freedesktop.DBus.Error.NoReply
kernel: bluetoothd[820]: segfault at 1a0 ip 00005633dd741323
sp 00007ffe51923650 error 4 in bluetoothd
#0 auto_config (data=0x56340361a120) at profiles/audio/a2dp.c:723
#1 endpoint_setconf_cb (setup=...) at profiles/audio/a2dp.c:768
#2 media_endpoint_cancel (request=...) at profiles/audio/media.c:208
#3 media_endpoint_cancel_all () at profiles/audio/media.c:216
#4 clear_endpoint () at profiles/audio/media.c:379
#5 endpoint_reply (user_data=...) at profiles/audio/media.c:407
(gdb) p *setup
$1 = {chan = 0x0, session = 0x0, ...,
setconf_cb = 0x5633dd74c780 <setconf_cb>, ..., ref = 2}
Commit 14750a2e48f4 ("audio/a2dp: Fix Access session device only when
its valid") fixed the same crash, but commit 77932f2dac1a
("profiles/audio: add nullity checks") moved avdtp_get_device() back
ahead of the checks. Just reordering is not enough though: the
a2dp_stream is still on sep->streams, so the aborted check passes and
setconf_cb() would then be called with a NULL session as well.
Fix it by making finalize_all() abort the pending Set Configuration
while the session is still valid: clearing the endpoint configuration
sends ClearConfiguration, removes the MediaTransport created for the
request and cancels the request, so auto_config() rejects the
configuration and frees the pending avdtp_stream and a2dp_stream, both
previously leaked together with the session reference held by the
latter. If no request is pending, the configuration is rejected
directly. auto_config() bails out when setup->session is NULL, as it
may still run from idle, and setconf_cb is cleared once called so it
cannot be called twice.
Removing a transport now cancels the endpoint requests pending for it,
so a late reply is ignored instead of leaving the transport registered,
which made the next connection fail with "Resource temporarily
unavailable".
Assisted-by: Claude:claude-opus-5-5
---
profiles/audio/a2dp.c | 132 +++++++++++++++++++++++++++--------------
profiles/audio/media.c | 20 ++++++-
2 files changed, 106 insertions(+), 46 deletions(-)
diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index 08a9e1c96e4f..37f6d4ae12ac 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -507,41 +507,6 @@ static void finalize_discover(struct a2dp_setup *s)
}
}
-static gboolean finalize_all(gpointer data)
-{
- struct a2dp_setup *s = data;
- struct avdtp_stream *stream = s->err ? NULL : s->stream;
- GSList *l;
-
- for (l = s->cb; l != NULL; ) {
- struct a2dp_setup_cb *cb = l->data;
-
- l = l->next;
-
- if (cb->discover_cb) {
- cb->discover_cb(s->session, s->seps,
- error_to_errno(s->err), cb->user_data);
- } else if (cb->select_cb) {
- cb->select_cb(s->session, s->sep, s->caps,
- error_to_errno(s->err), cb->user_data);
- } else if (cb->suspend_cb) {
- cb->suspend_cb(s->session,
- error_to_errno(s->err), cb->user_data);
- } else if (cb->resume_cb) {
- cb->resume_cb(s->session,
- error_to_errno(s->err), cb->user_data);
- } else if (cb->config_cb) {
- cb->config_cb(s->session, s->sep, stream,
- error_to_errno(s->err), cb->user_data);
- } else
- warn("setup_cb doesn't have any callback function");
-
- setup_cb_free(cb);
- }
-
- return FALSE;
-}
-
static struct a2dp_setup *find_setup_by_session(struct avdtp *session)
{
GSList *l;
@@ -711,6 +676,86 @@ static void stream_state_changed(struct avdtp_stream *stream,
sep->endpoint->clear_configuration(sep, dev, sep->user_data);
}
+static void setup_setconf_reply(struct a2dp_setup *setup,
+ struct avdtp_error *err)
+{
+ avdtp_set_configuration_cb cb = setup->setconf_cb;
+
+ if (!cb)
+ return;
+
+ setup->setconf_cb = NULL;
+
+ /* Rejecting the configuration frees the avdtp_stream */
+ if (err)
+ a2dp_stream_destroy(setup->sep, setup->stream);
+
+ cb(setup->session, setup->stream, err);
+
+ if (err)
+ setup->stream = NULL;
+}
+
+/* Reject a pending Set Configuration while setup->session is still valid */
+static void setup_abort_setconf(struct a2dp_setup *setup)
+{
+ struct a2dp_sep *sep = setup->sep;
+ struct avdtp_error err;
+
+ if (!setup->setconf_cb)
+ return;
+
+ /* Clearing the endpoint configuration cancels its pending request
+ * which rejects the configuration via auto_config().
+ */
+ if (sep->endpoint && sep->endpoint->clear_configuration)
+ sep->endpoint->clear_configuration(sep,
+ avdtp_get_device(setup->session),
+ sep->user_data);
+
+ /* Reject it if it was not pending on the endpoint */
+ avdtp_error_init(&err, AVDTP_MEDIA_CODEC,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ setup_setconf_reply(setup, &err);
+}
+
+static gboolean finalize_all(gpointer data)
+{
+ struct a2dp_setup *s = data;
+ struct avdtp_stream *stream = s->err ? NULL : s->stream;
+ GSList *l;
+
+ for (l = s->cb; l != NULL; ) {
+ struct a2dp_setup_cb *cb = l->data;
+
+ l = l->next;
+
+ if (cb->discover_cb) {
+ cb->discover_cb(s->session, s->seps,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->select_cb) {
+ cb->select_cb(s->session, s->sep, s->caps,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->suspend_cb) {
+ cb->suspend_cb(s->session,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->resume_cb) {
+ cb->resume_cb(s->session,
+ error_to_errno(s->err), cb->user_data);
+ } else if (cb->config_cb) {
+ cb->config_cb(s->session, s->sep, stream,
+ error_to_errno(s->err), cb->user_data);
+ } else
+ warn("setup_cb doesn't have any callback function");
+
+ setup_cb_free(cb);
+ }
+
+ setup_abort_setconf(s);
+
+ return FALSE;
+}
+
static gboolean auto_config(gpointer data)
{
struct a2dp_setup *setup = data;
@@ -718,6 +763,12 @@ static gboolean auto_config(gpointer data)
struct btd_service *service;
struct a2dp_stream *stream;
+ /* Check if the channel has been disconnected, in which case
+ * channel_free() has already rejected the configuration.
+ */
+ if (!setup->session)
+ goto done;
+
dev = avdtp_get_device(setup->session);
if (setup->sep->type == AVDTP_SEP_TYPE_SOURCE)
@@ -748,16 +799,7 @@ static gboolean auto_config(gpointer data)
}
done:
- if (setup->setconf_cb) {
- /* Rejecting the configuration frees the avdtp_stream */
- if (setup->err)
- a2dp_stream_destroy(setup->sep, setup->stream);
-
- setup->setconf_cb(setup->session, setup->stream, setup->err);
-
- if (setup->err)
- setup->stream = NULL;
- }
+ setup_setconf_reply(setup, setup->err);
finalize_config(setup);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index e8418280e60b..34ff251c7ef2 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -359,12 +359,26 @@ static struct media_adapter *find_adapter(struct btd_device *device)
return NULL;
}
+static int request_transport_cmp(gconstpointer data, gconstpointer user_data)
+{
+ const struct endpoint_request *request = data;
+
+ return request->transport == user_data ? 0 : -1;
+}
+
static void endpoint_remove_transport(struct media_endpoint *endpoint,
struct media_transport *transport)
{
+ GSList *l;
+
if (!endpoint || !transport)
return;
+ /* Cancel pending requests for the transport */
+ while ((l = g_slist_find_custom(endpoint->requests, transport,
+ request_transport_cmp)))
+ media_endpoint_cancel(l->data);
+
endpoint->transports = g_slist_remove(endpoint->transports, transport);
media_transport_destroy(transport);
}
@@ -431,7 +445,11 @@ static void endpoint_reply(DBusPendingCall *call, void *user_data)
if (dbus_message_is_method_call(request->msg,
MEDIA_ENDPOINT_INTERFACE,
"SetConfiguration")) {
- endpoint_remove_transport(endpoint, request->transport);
+ struct media_transport *transport = request->transport;
+
+ /* Detach so the request is not canceled */
+ request->transport = NULL;
+ endpoint_remove_transport(endpoint, transport);
error_code = a2dp_parse_config_error(err.name);
ret = &error_code;
size = 1;
--
2.55.0
next prev parent reply other threads:[~2026-10-07 19:52 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-07 19:52 [PATCH BlueZ v2 1/3] a2dp: Fix UAF after rejected SetConfiguration Luiz Augusto von Dentz
2026-10-07 19:52 ` Luiz Augusto von Dentz [this message]
2026-10-07 19:52 ` [PATCH BlueZ v2 3/3] test: Cover A2DP disconnection during SetConfiguration Luiz Augusto von Dentz
2026-10-08 0:07 ` [BlueZ,v2,1/3] a2dp: Fix UAF after rejected SetConfiguration bluez.test.bot
2026-10-08 15:00 ` [PATCH BlueZ v2 1/3] " patchwork-bot+bluetooth
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=20261007195206.350586-2-luiz.dentz@gmail.com \
--to=luiz.dentz@gmail.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox