libsdl-org / SDL

Simple Directmedia Layer
https://libsdl.org
zlib License
9.71k stars 1.8k forks source link

Can crash if Pipewire hotplug_loop_init() fails #10787

Closed smcv closed 1 month ago

smcv commented 1 month ago

While investigating a somewhat common crash seen in automated crash reports, I noticed a possible failure mode in SDL. If hotplug_loop_init() gets part way through initialization, then fails, there is a crash when libpipewire is unloaded.

(All diffs here are vs. SDL3 fa2c9c46 because that's the version currently used in Dota 2, which I'm using as a convenient example of a SDL3 game to test against; and I've also cherry-picked 0eff6361 to give threads better names. I'm using SDL3_DYNAMIC_API to force my patched SDL3 to be used.)

If I patch SDL to force hotplug_loop_init() to fail right at the end:

diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
index c258ed80d..73ead4f39 100644
--- a/src/audio/pipewire/SDL_pipewire.c
+++ b/src/audio/pipewire/SDL_pipewire.c
@@ -745,7 +745,7 @@ static int hotplug_loop_init(void)
         return SDL_SetError("Pipewire: Failed to start hotplug detection loop");
     }

-    return 0;
+    return SDL_SetError("Pipewire: intentionally failing hotplug_loop_init");
 }

 static void hotplug_loop_destroy(void)

then I see this crash:

Thread 2 (Thread 10670.10751 "SDLPwAudioPlug"):
#0  0x00007fffb0082e17 in on_remote_data (data=0x5555574f77f0, fd=85, mask=<optimized out>) at ../src/modules/module-protocol-native.c:1035
#1  0x00007fffb0737ebe in ?? ()
#2  0x0000000100000000 in ?? ()
#3  0x00005555574da9a8 in ?? ()
#4  0x0000000000000000 in ?? ()

Thread 1 (Thread 10670.10670 "dota2"):
#0  0x00007ffff7fc91cb in _dl_close_worker (map=<optimized out>, map@entry=0x5555574d6370, force=force@entry=false) at dl-close.c:741
#1  0x00007ffff7fc967b in _dl_close (_map=0x5555574d6370) at dl-close.c:793
#2  0x00007ffff7fc8523 in __GI__dl_catch_exception (exception=exception@entry=0x7fffffff5dd0, operate=0x7ffff7fc9640 <_dl_close>, args=0x5555574d6370) at dl-catch.c:241
#3  0x00007ffff7fc8679 in _dl_catch_error (objname=0x7fffffff5e38, errstring=0x7fffffff5e40, mallocedp=0x7fffffff5e37, operate=<optimized out>, args=<optimized out>) at dl-catch.c:260
#4  0x00007ffff7df59f3 in _dlerror_run (operate=<optimized out>, args=<optimized out>) at dlerror.c:138
#5  0x00007ffff7df5736 in __dlclose (handle=<optimized out>) at dlclose.c:31
#6  0x00007fffa418c229 in unref_plugin (plugin=0x55555743acb0) at ../src/pipewire/pipewire.c:171
#7  0x00007fffa418c445 in unref_plugin (plugin=<optimized out>) at ../src/pipewire/pipewire.c:167
#8  unref_handle (handle=0x5555574d6890) at ../src/pipewire/pipewire.c:212
#9  0x00007fffa418d07b in unref_handle (handle=<optimized out>) at ../src/pipewire/pipewire.c:206
#10 pw_deinit () at ../src/pipewire/pipewire.c:702
#11 0x00007ffff0a443e9 in deinit_pipewire_library () from target:/home/desktop/tmp/libSDL3.so.0
#12 0x00007ffff0a468dc in PIPEWIRE_Deinitialize () from target:/home/desktop/tmp/libSDL3.so.0
#13 0x00007ffff0a46928 in PipewireInitialize () from target:/home/desktop/tmp/libSDL3.so.0
#14 0x00007ffff0a469e7 in PIPEWIRE_PREFERRED_Init () from target:/home/desktop/tmp/libSDL3.so.0
#15 0x00007ffff083a536 in SDL_InitAudio () from target:/home/desktop/tmp/libSDL3.so.0
#16 0x00007ffff083270a in SDL_InitSubSystem_REAL () from target:/home/desktop/tmp/libSDL3.so.0
#17 0x00007fff8c67c9de in ?? () from target:/home/desktop/SteamLibrary/steamapps/common/dota 2 beta/game/bin/linuxsteamrt64/libsoundsystem.so
#18 0x0000000000000070 in ?? ()
#19 0x0000000000000000 in ?? ()

Analysis: after failing hotplug_loop_init(), the main thread calls PIPEWIRE_Deinitialize(), which unloads libpipewire and its plugins. However, hotplug_loop_init() already started the hotplug_loop thread, which is running libpipewire code. Cutting the floor out from under it is unlikely to go well.

e21f70c5 would make it impossible to reproduce this bug in Dota 2 because the Steam Linux Runtime 3.0 'sniper' version of libpipewire is older than 1.0.0, but I think that's only a workaround rather than a solution.

smcv commented 1 month ago

A theoretical crash from source code inspection that I was trying to reproduce here (but I couldn't quite work out how to do so) is:

pw_context_new() calls pw_data_loop_new() which also creates a thread, running do_loop() in Pipewire's src/pipewire/data-loop.c. This is also running Pipewire code, so it will similarly crash if the Pipewire library is unloaded.

The solution is to make sure that the PwContext was destroyed with pw_context_destroy() before the Pipewire library can be unloaded.

In SDL terms, the solution is to make sure that if we might have started hotplug_loop_init(), then we must call hotplug_loop_destroy() before the Pipewire library can be unloaded.

smcv commented 1 month ago

I can reproduce a different crash (not the same as the first one here, but possibly the same as the one in https://github.com/libsdl-org/SDL/issues/10787#issuecomment-2341871229) by forcing a failure immediately after pw_context_new():

diff --git a/src/audio/pipewire/SDL_pipewire.c b/src/audio/pipewire/SDL_pipewire.c
index c258ed80d..89c5ab7e0 100644
--- a/src/audio/pipewire/SDL_pipewire.c
+++ b/src/audio/pipewire/SDL_pipewire.c
@@ -718,7 +718,7 @@ static int hotplug_loop_init(void)
     }

     hotplug_context = PIPEWIRE_pw_context_new(PIPEWIRE_pw_thread_loop_get_loop(hotplug_loop), NULL, 0);
-    if (!hotplug_context) {
+    if (1 || !hotplug_context) {
         return SDL_SetError("Pipewire: Failed to create hotplug detection context (%i)", errno);
     }

Unfortunately I can't tell where this crash is:

Thread 2 "dota2" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 11557.11644]
0x00007fffb094d030 in ?? ()
(gdb) bt
#0  0x00007fffb094d030 in ?? ()
#1  0x0000000000000000 in ?? ()

but it's similarly happening while the main thread is unloading modules:

Thread 1 (Thread 11557.11557 "dota2"):
#0  0x00007ffff7fc91cb in _dl_close_worker (map=<optimized out>, map@entry=0x5555574b62a0, force=force@entry=false) at dl-close.c:741
#1  0x00007ffff7fc967b in _dl_close (_map=0x5555574b62a0) at dl-close.c:793
#2  0x00007ffff7fc8523 in __GI__dl_catch_exception (exception=exception@entry=0x7fffffff5dd0, operate=0x7ffff7fc9640 <_dl_close>, args=0x5555574b62a0) at dl-catch.c:241
#3  0x00007ffff7fc8679 in _dl_catch_error (objname=0x7fffffff5e38, errstring=0x7fffffff5e40, mallocedp=0x7fffffff5e37, operate=<optimized out>, args=<optimized out>) at dl-catch.c:260
#4  0x00007ffff7df59f3 in _dlerror_run (operate=<optimized out>, args=<optimized out>) at dlerror.c:138
#5  0x00007ffff7df5736 in __dlclose (handle=<optimized out>) at dlclose.c:31
#6  0x00007fffa408b229 in unref_plugin (plugin=0x55555741b020) at ../src/pipewire/pipewire.c:171
#7  0x00007fffa408b445 in unref_plugin (plugin=<optimized out>) at ../src/pipewire/pipewire.c:167
#8  unref_handle (handle=0x5555574b67c0) at ../src/pipewire/pipewire.c:212
#9  0x00007fffa408c07b in unref_handle (handle=<optimized out>) at ../src/pipewire/pipewire.c:206
#10 pw_deinit () at ../src/pipewire/pipewire.c:702
#11 0x00007ffff0a44373 in deinit_pipewire_library () from target:/home/desktop/tmp/libSDL3.so.0
#12 0x00007ffff0a4660c in PIPEWIRE_Deinitialize () from target:/home/desktop/tmp/libSDL3.so.0
#13 0x00007ffff0a46658 in PipewireInitialize () from target:/home/desktop/tmp/libSDL3.so.0
#14 0x00007ffff0a46717 in PIPEWIRE_PREFERRED_Init () from target:/home/desktop/tmp/libSDL3.so.0
#15 0x00007ffff083a536 in SDL_InitAudio () from target:/home/desktop/tmp/libSDL3.so.0
#16 0x00007ffff083270a in SDL_InitSubSystem_REAL () from target:/home/desktop/tmp/libSDL3.so.0
#17 0x00007fff8cc7c9de in ?? () from target:/home/desktop/SteamLibrary/steamapps/common/dota 2 beta/game/bin/linuxsteamrt64/libsoundsystem.so
#18 0x0000000000000070 in ?? ()
#19 0x0000000000000000 in ?? ()

I suspect that the reason I can't get a good backtrace for this is that 0x00007fffb094d030 is an address that was, until recently, occupied by a Pipewire plugin that has now been unloaded.

smcv commented 3 weeks ago
Thread 2 "dota2" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 11557.11644]
0x00007fffb094d030 in ?? ()
(gdb) bt
#0  0x00007fffb094d030 in ?? ()
#1  0x0000000000000000 in ?? ()

A follow-up for this: in Pipewire ≥ 1.0.1, this thread would have been labelled pw-data-loop, making it easier to diagnose this crash as Pipewire-related. Today's beta releases of Steam Linux Runtime 2.0 and 3.0 have a backport of that change into their older Pipewire versions.