* [PATCH BlueZ 1/2] a2dp: Fix UAF after rejected SetConfiguration
2026-10-01 14:34 [PATCH BlueZ 0/2] a2dp: Fix crashes around pending SetConfiguration Eduardo Alves
@ 2026-10-01 14:34 ` Eduardo Alves
2026-10-01 16:48 ` a2dp: Fix crashes around pending SetConfiguration bluez.test.bot
2026-10-01 14:34 ` [PATCH BlueZ 2/2] a2dp: Fix crash on NULL session in auto_config Eduardo Alves
2026-10-08 15:00 ` [PATCH BlueZ 0/2] a2dp: Fix crashes around pending SetConfiguration patchwork-bot+bluetooth
2 siblings, 1 reply; 5+ messages in thread
From: Eduardo Alves @ 2026-10-01 14:34 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Eduardo Alves
When the MediaEndpoint1 rejects SetConfiguration, auto_config() calls
setconf_cb() with an error and avdtp frees the avdtp_stream, but the
a2dp_stream wrapping it is left on sep->streams with a dangling stream
pointer. The next stream configured on the same session finds it in
a2dp_config() and reads the freed avdtp_stream:
ERROR: AddressSanitizer: heap-use-after-free
READ of size 4
#0 avdtp_stream_get_state profiles/audio/avdtp.c:3952
#1 a2dp_config profiles/audio/a2dp.c:3282
#2 select_complete profiles/audio/source.c:200
#3 finalize_select profiles/audio/a2dp.c:486
freed by thread T0 here:
#1 stream_free profiles/audio/avdtp.c:747
#2 setconf_cb profiles/audio/avdtp.c:1504
#3 auto_config profiles/audio/a2dp.c:752
#4 endpoint_setconf_cb profiles/audio/a2dp.c:768
Fix it by destroying the a2dp_stream when the configuration is
rejected, which also drops the session reference it holds.
This can be reproduced with test-functional using two VMs over btvirt,
where the A2DP Sink endpoint replies to SetConfiguration with an error
and the sink then connects to the source on the same session.
Assisted-by: Claude:claude-opus-5-5
---
profiles/audio/a2dp.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index efbe73f1b..e4b6f03fe 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -748,9 +748,17 @@ static gboolean auto_config(gpointer data)
}
done:
- if (setup->setconf_cb)
+ 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;
+ }
+
finalize_config(setup);
setup_error_set(setup, NULL);
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH BlueZ 2/2] a2dp: Fix crash on NULL session in auto_config
2026-10-01 14:34 [PATCH BlueZ 0/2] a2dp: Fix crashes around pending SetConfiguration Eduardo Alves
2026-10-01 14:34 ` [PATCH BlueZ 1/2] a2dp: Fix UAF after rejected SetConfiguration Eduardo Alves
@ 2026-10-01 14:34 ` Eduardo Alves
2026-10-08 15:00 ` [PATCH BlueZ 0/2] a2dp: Fix crashes around pending SetConfiguration patchwork-bot+bluetooth
2 siblings, 0 replies; 5+ messages in thread
From: Eduardo Alves @ 2026-10-01 14:34 UTC (permalink / raw)
To: linux-bluetooth; +Cc: Eduardo Alves
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 rejecting the pending Set Configuration in channel_free()
while the session is still valid, which also frees the pending
avdtp_stream and the a2dp_stream, both previously leaked together with
the session reference held by the latter, and by making auto_config()
bail out when setup->session is NULL, releasing its setup reference.
setconf_cb is cleared once called so it cannot be called twice.
channel_free() also clears the endpoint configuration, which sends
ClearConfiguration and removes the MediaTransport created for the
request. Otherwise a late successful reply leaves the transport
registered and the next connection fails with "Resource temporarily
unavailable". Since the transport may now be freed while the request
is pending, endpoint_remove_transport() resets the transport of the
pending requests so a late error reply does not free it twice.
The crash can be reproduced with test-functional using two VMs over
btvirt, where the A2DP Sink endpoint delays or never sends its
SetConfiguration reply and the A2DP Source disconnects meanwhile.
Assisted-by: Claude:claude-opus-5-5
---
profiles/audio/a2dp.c | 29 +++++++++++++++++++++++++++++
profiles/audio/media.c | 10 ++++++++++
2 files changed, 39 insertions(+)
diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c
index e4b6f03fe..be6dc4b99 100644
--- a/profiles/audio/a2dp.c
+++ b/profiles/audio/a2dp.c
@@ -718,6 +718,13 @@ static gboolean auto_config(gpointer data)
struct btd_service *service;
struct a2dp_stream *stream;
+ /* Check if the channel has been disconnected while waiting for the
+ * endpoint, 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)
@@ -754,6 +761,7 @@ done:
a2dp_stream_destroy(setup->sep, setup->stream);
setup->setconf_cb(setup->session, setup->stream, setup->err);
+ setup->setconf_cb = NULL;
if (setup->err)
setup->stream = NULL;
@@ -1932,6 +1940,27 @@ static void channel_free(void *data)
setup_ref(setup);
/* Finalize pending commands before we NULL setup->session */
finalize_setup_errno(setup, -ENOTCONN, finalize_all, NULL);
+ /* Reject pending Set Configuration since the endpoint reply
+ * can no longer be processed without setup->session.
+ */
+ if (setup->setconf_cb) {
+ struct a2dp_sep *sep = setup->sep;
+ struct avdtp_error err;
+
+ avdtp_error_init(&err, AVDTP_MEDIA_CODEC,
+ AVDTP_UNSUPPORTED_CONFIGURATION);
+ a2dp_stream_destroy(sep, setup->stream);
+ setup->setconf_cb(setup->session, setup->stream, &err);
+ setup->setconf_cb = NULL;
+ setup->stream = NULL;
+ /* Clear the transport set while waiting for the
+ * endpoint so it can be configured again.
+ */
+ if (sep->endpoint && sep->endpoint->clear_configuration)
+ sep->endpoint->clear_configuration(sep,
+ avdtp_get_device(setup->session),
+ sep->user_data);
+ }
avdtp_unref(setup->session);
setup->session = NULL;
setup_unref(setup);
diff --git a/profiles/audio/media.c b/profiles/audio/media.c
index dd1d4e571..d616034cb 100644
--- a/profiles/audio/media.c
+++ b/profiles/audio/media.c
@@ -353,9 +353,19 @@ static struct media_adapter *find_adapter(struct btd_device *device)
static void endpoint_remove_transport(struct media_endpoint *endpoint,
struct media_transport *transport)
{
+ GSList *l;
+
if (!endpoint || !transport)
return;
+ /* Pending requests must not refer to the transport once freed */
+ for (l = endpoint->requests; l; l = l->next) {
+ struct endpoint_request *request = l->data;
+
+ if (request->transport == transport)
+ request->transport = NULL;
+ }
+
endpoint->transports = g_slist_remove(endpoint->transports, transport);
media_transport_destroy(transport);
}
--
2.56.0
^ permalink raw reply related [flat|nested] 5+ messages in thread