Repository navigation
bluetoothd 5.87: SIGSEGV (use-after-free) in A2DP discovery finalization on audio device disconnect #2605
Description
Description
bluetoothd 5.87 crashes with a reproducible SIGSEGV (SEGV_MAPERR) every time an A2DP audio device disconnects. The fault lands at the same instruction offset on every occurrence, indicating a deterministic use-after-free while iterating a freed GSList of struct a2dp_setup_cb.
Expected: bluetoothd tears down the AVDTP session and frees the discovery callbacks without dereferencing freed memory.
Actual: bluetoothd receives SIGSEGV and is restarted by systemd on every disconnect. It also frequently takes down WirePlumber as collateral, because the crash happens inside the avdtp discovery callback path that PipeWire/WirePlumber is waiting on.
Regression: downgrading bluez / bluez-libs / bluez-utils to 5.86-6 completely removes the crash (same device, adapter and kernel, multiple connect/disconnect cycles). 5.87 is the first release containing the regressing changes.
Crash details
Signal: 11 (SEGV), si_code: SEGV_MAPERR. Identical stack on every run:
#0 0x000055f2a606a4f7 n/a (bluetoothd + 0x254f7)
#1 0x000055f2a606a3ec n/a (bluetoothd + 0x253ec)
#2 0x000055f2a606d964 n/a (bluetoothd + 0x28964)
#3 0x000055f2a60718ce n/a (bluetoothd + 0x2c8ce)
#4 libglib-2.0.so.0 + 0x61c76
#5 libglib-2.0.so.0 + 0x63ed7
#6 g_main_loop_run
#7 bluetoothd + 0x9def
#8 libc.so.6 + 0x27781
#9 __libc_start_main
#10 bluetoothd + 0xb3d5
Kernel log:
bluetoothd[672]: segfault at 55f7966d85a1 ip 000055f2a606a4f7 sp 00007ffcd520b710 error 4 in bluetoothd[254f7,55f2a604d000+11f000] likely on CPU 11 (core 5, socket 0)
Symbolized frames (via debuginfod.archlinux.org):
bluetoothd+0x254f7 -> discover_cb + 103 -> profiles/audio/a2dp.c:501
bluetoothd+0x253ec -> finalize_discovery + 92 -> profiles/audio/avdtp.c:1092
bluetoothd+0x28964 -> connection_lost + 100 -> profiles/audio/avdtp.c:1166
bluetoothd+0x2c8ce -> session_cb + 78 -> profiles/audio/avdtp.c:2372
Disassembly at the faulting instruction:
254f0: mov (%rbx),%r12 ; r12 = current list node
254f3: mov 0x8(%rbx),%rbx ; rbx = node->next
254f7: mov 0x8(%r12),%rax ; <-- FAULT: r12 is a dangling pointer
254fc: test %rax,%rax
a2dp.c:501 is if (!cb->discover_cb) inside finalize_discover() (a2dp.c:492), which was inlined into discover_cb():
static void finalize_discover(struct a2dp_setup *s)
{
GSList *l;
for (l = s->cb; l != NULL; ) {
struct a2dp_setup_cb *cb = l->data; /* a2dp.c:497 */
l = l->next; /* a2dp.c:499 */
if (!cb->discover_cb) /* a2dp.c:501 <-- crash */
continue;
cb->discover_cb(s->session, s->seps, error_to_errno(s->err),
cb->user_data);
setup_cb_free(cb); /* frees cb + drops setup ref */
}
}
struct a2dp_setup_cb layout confirms discover_cb sits at offset 8 (mov 0x8(%r12)), i.e. the crash reads cb->discover_cb from an already-freed cb.
Call path: session_cb (avdtp.c:2372) -> connection_lost (avdtp.c:1166) -> finalize_discovery (avdtp.c:1092) -> invokes discover->cb, which is A2DP's discover_cb(); that inlined finalize_discover() walks setup->cb and dereferences a freed a2dp_setup_cb. Classic "free the current list element while iterating, then touch it".
Likely contributing regression in 5.87 (remote-SEP cache never loads)
Independently of the UAF, 5.87 changed the remote-SEP cache parser in profiles/audio/a2dp.c (load_remote_sep) so the loop body is dead code, meaning cached remote SEPs are never loaded and AVDTP discovery has to be redone on every connection. That widens the window in which the discovery callback above is in flight when a disconnect arrives, and is consistent with the crash only appearing after 5.87.
profiles/audio/a2dp.c, load_remote_sep():
/* 5.86-6 (works): */
for (i = 0, size = strlen(caps); i < size; i += 2) {
/* 5.87-2: /
for (i = 0, size = strlen(caps); i < size && i >= 2; i += 2) {
/ ^^^^^^^^ i starts at 0, so this
-
is always false -> loop never -
executes -> cached SEP skipped */
The follow-up check if (i != size) continue; then always skips non-empty entries. Current upstream master has rewritten this block (adding size % 2 / buffer-size validation and dropping the i >= 2 term), which suggests the 5.87 form was not intended.
Caveat: the crash functions (finalize_discover in a2dp.c, finalize_discovery/connection_lost in avdtp.c) are byte-for-byte identical between 5.86, 5.87 and current master — so 5.87 does not introduce the UAF in these exact functions. The load_remote_sep change is a plausible trigger/amplifier, not proven to be the sole cause. Moving to 5.87 is nonetheless what turned it into a 10/10 reproducible crash.
Collateral damage
Because the crash occurs while WirePlumber holds a pending AVDTP discovery callback, WirePlumber segfaults right after (libspa-bluez5.so):
Process 21943 (wireplumber) of user 1000 dumped core.
#0 libspa-bluez5.so + 0x47fb1
#1 libspa-bluez5.so + 0x54f27
#2 wp_spa_device_enum_params_sync (libwireplumber-0.5.so.0)
Requested fix: fix the use-after-free in the A2DP discovery finalization path (iterate safely / hold a reference to setup while invoking callbacks), and restore correct remote-SEP cache parsing in load_remote_sep().
Possibly related: #2282 (5.87 SIGSEGV regression), #1951 (A2DP endpoint registration timing crash).
Local workaround: I downgraded to bluez/bluez-libs/bluez-utils 5.86-6 and pinned them via IgnorePkg in /etc/pacman.conf. No SIGSEGV since, with the same device exercised across multiple connect/disconnect cycles (bluetoothd PID unchanged, restart counter 0).
To reproduce
- Use BlueZ 5.87 with PipeWire/WirePlumber (sink endpoints registered by WirePlumber).
- Pair and connect an A2DP audio device (earbuds/headset).
- Play audio so an AVDTP stream is (or has been) established.
- Disconnect the device — either from the device side (powering the earbuds off) or via
bluetoothctl disconnect <MAC>. - bluetoothd segfaults immediately during disconnect handling.
Observed 10/10 times over two weeks (every single disconnect of this device). No crash on 5.86-6 with the same device, adapter and kernel.
Crash timestamps (local time, CST):
2026-09-15 22:13:54
2026-09-20 20:09:08
2026-09-20 22:07:06
2026-09-21 14:49:30
2026-09-25 02:49:06
2026-09-27 01:37:21
2026-09-27 23:06:11
2026-09-28 13:56:37
2026-09-28 23:29:24
2026-09-29 16:01:54
btmon trace
No response
Analysis focus
Audio / A2DP
Privacy
-
Skip anonymization — Send the raw decoded trace to the LLM without scrubbing MAC addresses or device names.
-
I understand this trace will be processed by a third-party LLM API and I have the right to share this data.
Other logs
journalctl -u bluetooth --boot 0 (around the crash, BlueZ 5.87):
bluetoothd[672]: segfault at 55f7966d85a1 ip 000055f2a606a4f7 sp 00007ffcd520b710 error 4 in bluetoothd[254f7,55f2a604d000+11f000] likely on CPU 11 (core 5, socket 0)
systemd-coredump[245760]: Process 672 (bluetoothd) of user 0 terminated abnormally with signal 11/SEGV, processing...
bluetooth.service: Main process exited, code=dumped, status=11/SEGV
bluetooth.service: Failed with result 'core-dump'.
bluetooth.service: Scheduled restart job, restart counter is at 1.
Disconnect path preceding the crash:
spa.bluez5.sink.media: connection (/org/bluez/hci1/dev_90_4C_02_36_F7_73/fd0) terminated unexpectedly
pw.node: (bluez_output.90_4C_02_36_F7_73.1-94) running -> error (Received error event)
spa.bluez5: Failure in Bluetooth audio transport /org/bluez/hci1/dev_90_4C_02_36_F7_73/fd0
Core dumps are retained locally (/var/lib/systemd/coredump/); can run further gdb queries or provide them on request.
Versions
- BlueZ version: 5.87-2 (bluez-libs 5.87-2, bluez-utils 5.87-2); works on 5.86-6
- Kernel version: 7.2.6-arch2-1 (also observed on 7.2.7-arch1-1)
- Problematic device: iQOO TWS 1i (90:4C:02:36:F7:73), A2DP sink (Audio Sink, UUID 0000110b)
Additional environment:
- Distribution: Arch Linux (rolling)
- PipeWire: 1:1.6.9-1
- WirePlumber: 0.5.17-2
- Adapter in use: Realtek RTL8761 (hci1)
- Second adapter present: Intel ibt-0041-0041 (hci0)
- dbus: dbus-broker
Activity
MLAN1O commentedon Oct 11, 2026
Same family of crashes here on Fedora 44, BlueZ
5.87+1.git8750129efca8-1.fc44,different headset (SOUNDPEATS Wings2
84:AC:60:69:DC:2D, adapterE0:AD:47:10:26:1D,PipeWire 1.6.8 + WirePlumber 0.5.14).
Trigger (one step earlier than disconnect)
BR/EDR connects fine, but as soon as playback opens the A2DP transport,
bluetoothd dies and takes the connection down:
User-visible symptom: headset shows as connected, but the moment audio starts,
it disconnects.
pactlnever keeps a stablebluez_output.Two alternating crash signatures in the same ~45s window
1. SIGSEGV in
transport_a2dp_suspendpathKernel log:
2. SIGABRT in
release_endpoint(same stack as #2361)Crash sequence (
coredumpctl)Each crash is followed by
bluetooth.service: Failed with result 'core-dump'and a systemd restart that re-registers all ~20 PipeWire endpoints
(
sender=:1.65 path=/MediaEndpoint/A2DP*).Environment
WirePlumber Bluetooth policy on this host:
Workaround / verification
Downgraded to
5.86-4.fc44and pinned with versionlock:Result on 5.86:
bluetoothdPID stable (no restart, 0core-dumpsince restart)bluetoothctl info→Connected: yes+Audio Sink/Advanced Audio DistributionUUIDspactl list sinks short→bluez_output.84_AC_60_69_DC_2D.1present and defaultjournalctl -u bluetooth→/org/bluez/hci0/dev_84_AC_60_69_DC_2D/fd0: fd(42) readywith no crash afterSo the same 5.86 → 5.87 regression story as reported here.
Notes
specific gdb queries or provide them on request.
Disclosure: diagnosis drafted with an AI coding assistant, all logs/versions above are from my own machine and verified by me.
frey2526 commentedon Oct 12, 2026
Same
libspa-bluez5.socollateral crash, different trigger: resume from suspend (no device disconnect involved).Environment: Omarchy (Arch), BlueZ 5.87-2, PipeWire 1.6.8-1, WirePlumber 0.5.17-1, kernel 7.2.5-3-omarchy, dbus-broker. A2DP headset (JBL Wave Beam 2) paired.
5/5 crashes over 5 days, every one 0-1s after resume, while bluetoothd re-initialises the adapter and re-registers the ~20
MediaEndpoint/A2DP*endpoints:Crashing thread is
wp-client; same library/function region as the collateral crash above (offset differs by a few KB):Note:
bluetoothditself does not crash in this variant — only WirePlumber does. No OOM (3 GiB free); no system-sleep hook touches bluetooth/pipewire. Consistent with the endpoint unregister/re-register churn on 5.87; 5.86 is the reported fix.libspa-bluez5.sohas no debug symbols on debuginfod.archlinux.org (404 for its build-id), so frames remain unsymbolized.Filed by big-pickle via opencode.