From 882737b077e1bd3757595de92716cc8a8ce53efb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barnab=C3=A1s=20P=C5=91cze?= Date: Wed, 20 Aug 2025 23:12:13 +0200 Subject: [PATCH] pipewire: module-link-factory: cancel async work in link's destroy event When a link enters the "ERROR" state, it is scheduled for destruction in `module-link-factory.c:link_state_changed()`, which queues `destroy_link()` to be executed on the context's work queue. However, if the link is destroyed by means of `pw_impl_link_destroy()` directly after that, then `link_destroy()` unregisters the associated `pw_global`'s event hook, resulting in `global_destroy()` not being called when `pw_impl_link_destroy()` proceeds to call `pw_global_destroy()` some time later. This causes the scheduled async work to not be cancelled. When it runs later, it will trigger a use-after-free since the `link_data` object is directly tied to the `pw_impl_link` object. For example, if the link is destroyed when the client disconnects: ==259313==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ce753028af0 at pc 0x7f475354a565 bp 0x7ffd71501930 sp 0x7ffd71501920 READ of size 8 at 0x7ce753028af0 thread T0 #0 0x7f475354a564 in destroy_link ../src/modules/module-link-factory.c:253 #1 0x7f475575a234 in process_work_queue ../src/pipewire/work-queue.c:67 #2 0x7b47504e7f24 in source_event_func ../spa/plugins/support/loop.c:1011 [...] 0x7ce753028af0 is located 1136 bytes inside of 1208-byte region [0x7ce753028680,0x7ce753028b38) freed by thread T0 here: #0 0x7f475631f79d in free /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:51 #1 0x7f4755594a44 in pw_impl_link_destroy ../src/pipewire/impl-link.c:1742 #2 0x7f475569dc11 in do_destroy_link ../src/pipewire/impl-port.c:1386 #3 0x7f47556a428b in pw_impl_port_for_each_link ../src/pipewire/impl-port.c:1673 #4 0x7f475569dc3e in pw_impl_port_unlink ../src/pipewire/impl-port.c:1392 #5 0x7f47556a02d8 in pw_impl_port_destroy ../src/pipewire/impl-port.c:1453 #6 0x7f4755634f79 in pw_impl_node_destroy ../src/pipewire/impl-node.c:2447 #7 0x7b474f722ba8 in client_node_resource_destroy ../src/modules/module-client-node/client-node.c:1253 #8 0x7f47556d7c6c in pw_resource_destroy ../src/pipewire/resource.c:325 #9 0x7f475545f07d in destroy_resource ../src/pipewire/impl-client.c:627 #10 0x7f47554550cd in pw_map_for_each ../src/pipewire/map.h:222 #11 0x7f4755460aa4 in pw_impl_client_destroy ../src/pipewire/impl-client.c:681 #12 0x7b474fb0658b in handle_client_error ../src/modules/module-protocol-native.c:471 [...] Fix this by cancelling the work queue item in `link_destroy()`, which should always run, regardless of the ordering of events. Fixes #4691 --- src/modules/module-link-factory.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index 039ed16ff..8daa3b11c 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -193,8 +193,7 @@ static const struct pw_resource_events resource_events = { static void global_destroy(void *data) { struct link_data *ld = data; - struct factory_data *d = ld->data; - pw_work_queue_cancel(d->work, ld, SPA_ID_INVALID); + spa_hook_remove(&ld->global_listener); ld->global = NULL; } @@ -213,6 +212,7 @@ static void link_destroy(void *data) spa_hook_remove(&ld->global_listener); if (ld->resource) spa_hook_remove(&ld->resource_listener); + pw_work_queue_cancel(ld->data->work, ld, SPA_ID_INVALID); } static void link_initialized(void *data)