Summary
The sniper runtime bundles pipewire 0.3.65-3+deb12u1~steamrt3.3+bsrt3.1 (upstream 0.3.65, released 2023-01-24). That version contains a known data race on SPA_IO_Buffers between the main thread and the pw-data-loop RT thread during port/format renegotiation, fixed upstream in PipeWire 1.2.2 (2024-07-29).
On a host with a modern PipeWire daemon, a Bluetooth headset switching profile (A2DP 48 kHz stereo <-> HSP/HFP 16 kHz mono) forces a mid-stream format renegotiation. The bundled 0.3.65 client then either SIGSEGVs in libspa-audioconvert.so or deadlocks the game.
In CS2 this reproduces on demand: with the headset connected, open Settings -> Audio and click the audio device selector dropdown. Enumerating devices re-creates the streams and drives the renegotiation straight into the racy window.
Observed with Counter-Strike 2, but this is a runtime-level defect and should affect any sniper game using audio with a Bluetooth headset.
Environment
| Component |
Version |
| OS |
Fedora 44 |
| Kernel |
7.1.8-200.fc44.x86_64 |
| CPU / GPU |
Ryzen 9 5900X / Radeon RX 6700 XT (Mesa 26.1.7) |
| Host PipeWire |
1.6.8 |
| Host WirePlumber |
0.5.14 |
| Host BlueZ |
5.87 |
| sniper |
3.0.20260805.254768, pressure-vessel 0.20260805.0 |
| Bundled PipeWire |
0.3.65-3+deb12u1~steamrt3.3+bsrt3.1 |
From sniper_platform_3.0.20260805.254768/files/manifest.dpkg:
libpipewire-0.3-0:amd64 0.3.65-3+deb12u1~steamrt3.3+bsrt3.1
libpipewire-0.3-modules:amd64 0.3.65-3+deb12u1~steamrt3.3+bsrt3.1
libspa-0.2-modules:amd64 0.3.65-3+deb12u1~steamrt3.3+bsrt3.1
pipewire-bin 0.3.65-3+deb12u1~steamrt3.3+bsrt3.1
The host daemon flags the client as outdated on every stream creation:
pipewire[2300]: mod.client-node: detected old client version 4
Failure mode 1: SIGSEGV in libspa-audioconvert
kernel: pw-data-loop[1044288]: segfault at 21 ip 00007fdd599bba71 sp 00007fdd2c3fa270 \
error 4 in libspa-audioconvert.so[31a71,7fdd5999a000+3f000]
systemd-coredump: Process 1044176 (cs2) of user 1000 dumped core.
#0 0x00007fdd599bba71 n/a (/usr/lib/x86_64-linux-gnu/spa-0.2/audioconvert/libspa-audioconvert.so + 0x31a71)
Note the path /usr/lib/x86_64-linux-gnu/...: inside the pressure-vessel container, i.e. the runtime's bundled SPA plugin, not the host's /usr/lib64/.
objdump -d of the bundled libspa-audioconvert.so at the faulting offset:
31a54: cmp %r15d,0x463c(%r12) ; loop bound (port->blocks)
31a5e: mov 0x18(%r14),%rsi
31a65: lea (%rax,%rax,4),%rdx ; i * 5
31a69: mov 0x10(%rsi),%rax ; -> datas array base
31a6d: lea (%rax,%rdx,8),%rbx ; &datas[i], stride 40 == sizeof(struct spa_data)
31a71: mov 0x20(%rbx),%rax ; <-- FAULTS reading spa_data.chunk
31a75: movl $0x0,(%rax) ; chunk->offset = 0
31a7f: movl $0x0,0x4(%rax) ; chunk->size = 0
This is impl_node_process() in spa/plugins/audioconvert/audioconvert.c:
for (j = 0; j < port->blocks; j++) {
bd = &buf->buf->datas[j];
bd->chunk->offset = 0;
bd->chunk->size = 0;
Fault address 0x21 with error 4 (userspace read of an unmapped page): the datas array base resolved to 1, i.e. a stale/freed buffer touched during renegotiation.
Failure mode 2: deadlock (same trigger)
On a subsequent launch the game hangs instead of crashing:
1044793 cs2 [S] futex_do_wait
1044924 pw-data-loop [S] ep_poll
1044926 pw-data-loop [S] ep_poll
1044930 pw-data-loop [S] ep_poll
Three pw-data-loop threads in a single client, consistent with repeated stream re-creation without teardown. Server side:
pipewire-pulse: mod.protocol-pulse: [] timeout on stream ... channel:0
... channels 0-7 all time out
This also wedges the host audio graph: host-side wpctl status hangs, and the desktop volume applet shows two simultaneously-selected default sinks until wireplumber/pipewire are restarted.
Trigger evidence
kernel: Bluetooth: hci0: SCO packet for unknown connection handle 257
bluetoothd: btd_service_connect() a2dp-source profile connect failed for XX:XX:XX:XX:XX:XX: Device or resource busy
wireplumber: <WpSiStandardLink> link failed: item deactivated before format was set
At crash time the card was stuck in the voice profile, with A2DP not offered at all:
Active Profile: headset-head-unit
off : Off
headset-head-unit-cvsd : Headset Head Unit (HSP/HFP, codec CVSD)
headset-head-unit : Headset Head Unit (HSP/HFP, codec MSBC)
Upstream fix
Upstream issue: https://gitlab.freedesktop.org/pipewire/pipewire/-/work_items/4094 (closed 2024-08-01). Different surface trigger (EasyEffects creating a new output stream), same underlying race.
The defect is not a missing null check at the crash site. It is a race on port->io / SPA_IO_Buffers between the main thread performing format renegotiation and the RT data loop running impl_node_process(). Fix commits, all dated 2024-07-29:
| Commit |
First release |
Subject |
628f292fb877440f9531cc452f81b59ac3471637 |
1.2.2 |
audioconvert: set IO_Buffers only when buffers are negotiated |
0d7c20760e56fc60db13f7d972baed4bc0760767 |
1.2.2 |
impl-port: improve IO_Buffers management on ports |
61dcd8dedec3c56375e333113156ec7fbd0cd372 |
1.3.81 / 1.4.0 |
audioconvert: set IO_Buffers only when buffers are negotiated (mainline) |
The fix routes port->io assignment through the RT data loop, and clears SPA_IO_Buffers on SPA_PARAM_Format change before the port leaves the data-thread schedule, closing exactly the window an A2DP <-> HFP switch hits.
Verified by ancestry check against the upstream repo:
$ git merge-base --is-ancestor 628f292fb 0.3.65 # -> false (0.3.65 predates the fix)
$ git merge-base --is-ancestor 61dcd8ded 1.6.8 # -> true
0.3.65 is dated 2023-01-24, roughly 18 months before the fix landed.
Steps to reproduce
Deterministic path, via CS2's in-game device enumeration:
- Host running a modern PipeWire (1.2.2+) and WirePlumber, with
bluetooth.autoswitch-to-headset-profile at its default true.
- Pair a Bluetooth headset exposing both A2DP and HSP/HFP, and connect it.
- Launch CS2 (sniper runtime).
- Open Settings -> Audio and click the audio device selector dropdown.
- The game segfaults in
libspa-audioconvert.so, or deadlocks with orphaned pw-data-loop threads.
Opening that dropdown makes the game enumerate and open the available audio devices, which tears down and re-creates streams and drives the port/format renegotiation into the racy window. This is consistent with the three orphaned pw-data-loop threads observed in failure mode 2: repeated stream re-creation without teardown.
The same crash also occurs without touching the menu, whenever the headset microphone is acquired and the profile autoswitches A2DP -> HSP/HFP mid-stream. The dropdown is simply the reliable way to trigger it on demand.
Workaround
Prevent the renegotiation entirely by disabling profile autoswitching on the host:
wpctl settings --save bluetooth.autoswitch-to-headset-profile false
Then reconnect the headset so it returns to a2dp-sink. This costs the headset microphone, since HFP is what was disabled, so it is not viable for players who need headset voice chat.
Request
Backport a current PipeWire into sniper. The steamrt README's backporting policy already names PipeWire as suitable for this (stable SONAME libpipewire-0.3.so.0), and recent sniper releases have carried updates for SDL, Vulkan-Loader, nss and others while PipeWire has stayed at the 0.3.65 baseline.
Possibly related, never root-caused: ValveSoftware/csgo-osx-linux#3349 ("Game Freeze when bluetooth headset is connected", closed 2023), which matches failure mode 2 and was closed after the reporter removed PipeWire entirely.
Summary
The sniper runtime bundles
pipewire 0.3.65-3+deb12u1~steamrt3.3+bsrt3.1(upstream 0.3.65, released 2023-01-24). That version contains a known data race onSPA_IO_Buffersbetween the main thread and thepw-data-loopRT thread during port/format renegotiation, fixed upstream in PipeWire 1.2.2 (2024-07-29).On a host with a modern PipeWire daemon, a Bluetooth headset switching profile (A2DP 48 kHz stereo <-> HSP/HFP 16 kHz mono) forces a mid-stream format renegotiation. The bundled 0.3.65 client then either SIGSEGVs in
libspa-audioconvert.soor deadlocks the game.In CS2 this reproduces on demand: with the headset connected, open Settings -> Audio and click the audio device selector dropdown. Enumerating devices re-creates the streams and drives the renegotiation straight into the racy window.
Observed with Counter-Strike 2, but this is a runtime-level defect and should affect any sniper game using audio with a Bluetooth headset.
Environment
3.0.20260805.254768, pressure-vessel0.20260805.00.3.65-3+deb12u1~steamrt3.3+bsrt3.1From
sniper_platform_3.0.20260805.254768/files/manifest.dpkg:The host daemon flags the client as outdated on every stream creation:
Failure mode 1: SIGSEGV in libspa-audioconvert
Note the path
/usr/lib/x86_64-linux-gnu/...: inside the pressure-vessel container, i.e. the runtime's bundled SPA plugin, not the host's/usr/lib64/.objdump -dof the bundledlibspa-audioconvert.soat the faulting offset:This is
impl_node_process()inspa/plugins/audioconvert/audioconvert.c:Fault address
0x21witherror 4(userspace read of an unmapped page): thedatasarray base resolved to1, i.e. a stale/freed buffer touched during renegotiation.Failure mode 2: deadlock (same trigger)
On a subsequent launch the game hangs instead of crashing:
Three
pw-data-loopthreads in a single client, consistent with repeated stream re-creation without teardown. Server side:This also wedges the host audio graph: host-side
wpctl statushangs, and the desktop volume applet shows two simultaneously-selected default sinks untilwireplumber/pipewireare restarted.Trigger evidence
At crash time the card was stuck in the voice profile, with A2DP not offered at all:
Upstream fix
Upstream issue: https://gitlab.freedesktop.org/pipewire/pipewire/-/work_items/4094 (closed 2024-08-01). Different surface trigger (EasyEffects creating a new output stream), same underlying race.
The defect is not a missing null check at the crash site. It is a race on
port->io/SPA_IO_Buffersbetween the main thread performing format renegotiation and the RT data loop runningimpl_node_process(). Fix commits, all dated 2024-07-29:628f292fb877440f9531cc452f81b59ac34716370d7c20760e56fc60db13f7d972baed4bc076076761dcd8dedec3c56375e333113156ec7fbd0cd372The fix routes
port->ioassignment through the RT data loop, and clearsSPA_IO_BuffersonSPA_PARAM_Formatchange before the port leaves the data-thread schedule, closing exactly the window an A2DP <-> HFP switch hits.Verified by ancestry check against the upstream repo:
0.3.65 is dated 2023-01-24, roughly 18 months before the fix landed.
Steps to reproduce
Deterministic path, via CS2's in-game device enumeration:
bluetooth.autoswitch-to-headset-profileat its defaulttrue.libspa-audioconvert.so, or deadlocks with orphanedpw-data-loopthreads.Opening that dropdown makes the game enumerate and open the available audio devices, which tears down and re-creates streams and drives the port/format renegotiation into the racy window. This is consistent with the three orphaned
pw-data-loopthreads observed in failure mode 2: repeated stream re-creation without teardown.The same crash also occurs without touching the menu, whenever the headset microphone is acquired and the profile autoswitches A2DP -> HSP/HFP mid-stream. The dropdown is simply the reliable way to trigger it on demand.
Workaround
Prevent the renegotiation entirely by disabling profile autoswitching on the host:
wpctl settings --save bluetooth.autoswitch-to-headset-profile falseThen reconnect the headset so it returns to
a2dp-sink. This costs the headset microphone, since HFP is what was disabled, so it is not viable for players who need headset voice chat.Request
Backport a current PipeWire into sniper. The steamrt README's backporting policy already names PipeWire as suitable for this (stable SONAME
libpipewire-0.3.so.0), and recent sniper releases have carried updates for SDL, Vulkan-Loader, nss and others while PipeWire has stayed at the 0.3.65 baseline.Possibly related, never root-caused: ValveSoftware/csgo-osx-linux#3349 ("Game Freeze when bluetooth headset is connected", closed 2023), which matches failure mode 2 and was closed after the reporter removed PipeWire entirely.