Skip to content

bluetoothd 5.87: SIGSEGV (use-after-free) in A2DP discovery finalization on audio device disconnect #2605

Open

Description

@Azusa-mikan

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

  1. Use BlueZ 5.87 with PipeWire/WirePlumber (sink endpoints registered by WirePlumber).
  2. Pair and connect an A2DP audio device (earbuds/headset).
  3. Play audio so an AVDTP stream is (or has been) established.
  4. Disconnect the device — either from the device side (powering the earbuds off) or via bluetoothctl disconnect <MAC>.
  5. 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

  1. MLAN1O commented on Oct 11, 2026

    @MLAN1O

    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, adapter E0: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:

    bluetoothd[3970662]: /org/bluez/hci0/dev_84_AC_60_69_DC_2D/sep2/fd0: fd(39) ready
    bluetoothd[3970662]: Disconnected from D-Bus. Exiting.
    bluetoothd[3970662]: dbus[3970662]: arguments to dbus_message_new_method_call() were incorrect, assertion "destination == NULL || _dbus_check_is_valid_bus_name (destination)" failed in file ../dbus/dbus-message.c line 1387.
    

    User-visible symptom: headset shows as connected, but the moment audio starts,
    it disconnects. pactl never keeps a stable bluez_output.

    Two alternating crash signatures in the same ~45s window

    1. SIGSEGV in transport_a2dp_suspend path

    #0  queue_remove (bluetoothd + 0xe1698)
    #1  media_endpoint_remove (bluetoothd + 0xb03a)
    #2  a2dp_unregister_sep
    #3  transport_a2dp_suspend
    #4  media_transport_free
    #5  remove_interface
    #6  g_dbus_unregister_interface
    #7  media_transport_destroy
    #8  clear_configuration
    #9  release_endpoint
    #10 path_free
    #11 remove_interface
    #12 g_dbus_unregister_interface
    #13 unload_driver
    #15 a2dp_exit
    #16 main
    

    Kernel log:

    bluetoothd[3970410]: segfault at 59 ip 0000565378e42698 sp 00007ffd83f9a7e0 error 4 in bluetoothd[e1698,565378d61000+129000] likely on CPU 11 (core 3, socket 0)
    

    2. SIGABRT in release_endpoint (same stack as #2361)

    #0  __pthread_kill_implementation (libc.so.6)
    #1  raise (libc.so.6)
    #2  abort (libc.so.6)
    #3  _dbus_abort.cold (libdbus-1.so.3)
    #4  _dbus_warn_check_failed (libdbus-1.so.3)
    #5  dbus_message_new_method_call (libdbus-1.so.3)
    #6  release_endpoint (bluetoothd)
    #7  path_free
    #8  remove_interface
    #9  g_dbus_unregister_interface
    #10 unload_driver
    #11 g_slist_foreach (libglib-2.0.so.0)
    #12 a2dp_exit
    #13 main
    

    Crash sequence (coredumpctl)

    Sun 2026-10-11 00:27:48 -03  SIGSEGV  /usr/libexec/bluetooth/bluetoothd
    Sun 2026-10-11 00:28:00 -03  SIGSEGV  /usr/libexec/bluetooth/bluetoothd
    Sun 2026-10-11 00:28:16 -03  SIGABRT  /usr/libexec/bluetooth/bluetoothd
    Sun 2026-10-11 00:28:25 -03  SIGSEGV  /usr/libexec/bluetooth/bluetoothd
    Sun 2026-10-11 00:28:33 -03  SIGABRT  /usr/libexec/bluetooth/bluetoothd
    

    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

    bluez-5.87+1.git8750129efca8-1.fc44.x86_64
    bluez-libs-5.87+1.git8750129efca8-1.fc44.x86_64 (+ i686)
    bluez-obexd-5.87+1.git8750129efca8-1.fc44.x86_64
    bluez-cups-5.87+1.git8750129efca8-1.fc44.x86_64
    pipewire-1.6.8-1.fc44.x86_64
    wireplumber-0.5.14-1.fc44.x86_64
    dbus-broker-37-8.fc44.x86_64, dbus-1.16.2-1.fc44.x86_64
    Fedora 44 KDE Plasma (Wayland), kernel via Fedora 44 stock
    

    WirePlumber Bluetooth policy on this host:

    monitor.bluez.properties = { bluez5.roles = [ a2dp_sink a2dp_source ] }
    wireplumber.settings = { bluetooth.autoswitch-to-headset-profile = false }
    

    Workaround / verification

    Downgraded to 5.86-4.fc44 and pinned with versionlock:

    sudo dnf downgrade -y bluez-5.86-4.fc44 bluez-libs-5.86-4.fc44 \
      bluez-obexd-5.86-4.fc44 bluez-cups-5.86-4.fc44
    sudo systemctl restart bluetooth
    sudo dnf versionlock add bluez bluez-libs bluez-obexd bluez-cups

    Result on 5.86:

    • Same bluetoothd PID stable (no restart, 0 core-dump since restart)
    • bluetoothctl info → Connected: yes + Audio Sink / Advanced Audio Distribution UUIDs
    • pactl list sinks short → bluez_output.84_AC_60_69_DC_2D.1 present and default
    • journalctl -u bluetooth → /org/bluez/hci0/dev_84_AC_60_69_DC_2D/fd0: fd(42) ready with no crash after
    • Headset holds the connection through playback

    So the same 5.86 → 5.87 regression story as reported here.

    Notes

    • No btmon trace retained, but cores are in systemd-coredump — happy to run
      specific gdb queries or provide them on request.
    • Happy to test a patch (can unpin 5.86 temporarily for verification).

    Disclosure: diagnosis drafted with an AI coding assistant, all logs/versions above are from my own machine and verified by me.

  2. frey2526 commented on Oct 12, 2026

    @frey2526

    Same libspa-bluez5.so collateral 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:

    2026-10-08 20:53:07  SIGSEGV
    2026-10-09 16:18:07  SIGSEGV
    2026-10-10 00:46:41  SIGSEGV
    2026-10-11 20:22:19  SIGSEGV
    2026-10-11 21:16:26  SIGSEGV   <- all within ~1s of "System returned from sleep"
    

    Crashing thread is wp-client; same library/function region as the collateral crash above (offset differs by a few KB):

    wp-client[14150]: segfault at 1500000054 ip ...47f71 error 4 in libspa-bluez5.so[47f71,...]
    #0 libspa-bluez5.so + 0x47f71
    #1 libspa-bluez5.so + 0x54ee7
    #2 libpipewire-module-client-device.so + 0x8ce0
    #3 libpipewire-module-protocol-native.so + 0xa686
    

    Note: bluetoothd itself 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.so has no debug symbols on debuginfod.archlinux.org (404 for its build-id), so frames remain unsymbolized.

    Filed by big-pickle via opencode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions