Skip to content

Commit 2f747a7

Browse files
committed
protocol-native: add 5s handshake timeout to prevent indefinite hangs
When the PipeWire daemon is alive but hung (e.g. after lockfile staleness or during a deadlock), clients connecting via pw_protocol_native_connect_local_socket() use SOCK_NONBLOCK. The connect() succeeds immediately but the protocol handshake never completes, causing wpctl, pactl, and libwireplumber clients to hang indefinitely. This adds a spa timer to impl_connect_fd() that fires after CONNECT_TIMEOUT_MS (5000 ms). If the connection hasn't completed by then, the timer callback destroys the IO source and emits -ETIMEDOUT to the core, unblocking the client. Fixes: wpctl/pactl hangs when pipewire daemon is alive but unresponsive (e.g. after stale lockfile) CVE: N/A See: https://gitlab.freedesktop.org/pipewire/wireplumber/-/work_items
1 parent 85df114 commit 2f747a7

1 file changed

Lines changed: 48 additions & 1 deletion

File tree

‎src/modules/module-protocol-native.c‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <sys/stat.h>
1515
#include <fcntl.h>
1616
#include <sys/file.h>
17+
#include <time.h>
1718
#include <ctype.h>
1819
#include <limits.h>
1920
#ifdef HAVE_PWD_H
@@ -210,6 +211,7 @@ struct client {
210211
struct pw_context *context;
211212

212213
struct spa_source *source;
214+
struct spa_source *timer;
213215

214216
struct pw_protocol_native_connection *connection;
215217
struct spa_hook conn_listener;
@@ -224,6 +226,36 @@ struct client {
224226
unsigned int paused:1;
225227
};
226228

229+
/* Connection handshake timeout in milliseconds */
230+
#define CONNECT_TIMEOUT_MS 5000
231+
232+
static void on_connect_timeout(void *data, uint64_t expirations)
233+
{
234+
struct client *impl = data;
235+
struct pw_core *this = impl->this.core;
236+
struct pw_proxy *core_proxy = (struct pw_proxy*)this;
237+
struct pw_context *context = pw_core_get_context(this);
238+
struct pw_loop *loop = pw_context_get_main_loop(context);
239+
240+
if (impl->connected || impl->disconnecting)
241+
return;
242+
243+
pw_log_warn("%p: connection handshake timed out after %d ms",
244+
impl, CONNECT_TIMEOUT_MS);
245+
246+
/* Clean up IO source */
247+
if (impl->source) {
248+
pw_loop_destroy_source(loop, impl->source);
249+
impl->source = NULL;
250+
}
251+
impl->timer = NULL;
252+
253+
/* Notify connection error to core */
254+
pw_proxy_notify(core_proxy,
255+
struct pw_core_events, error, 0, 0,
256+
0, -ETIMEDOUT, "connection handshake timeout");
257+
}
258+
227259
static void client_unref(struct client *impl)
228260
{
229261
if (--impl->ref == 0)
@@ -1205,18 +1237,28 @@ on_remote_data(void *data, int fd, uint32_t mask)
12051237
static int impl_connect_fd(struct pw_protocol_client *client, int fd, bool do_close)
12061238
{
12071239
struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
1240+
struct pw_context *context = pw_core_get_context(impl->this.core);
1241+
struct pw_loop *loop = pw_context_get_main_loop(context);
1242+
struct timespec value;
12081243

12091244
impl->connected = false;
12101245
impl->disconnecting = false;
12111246

12121247
pw_protocol_native_connection_set_fd(impl->connection, fd);
1213-
impl->source = pw_loop_add_io(impl->context->main_loop,
1248+
impl->source = pw_loop_add_io(loop,
12141249
fd,
12151250
SPA_IO_IN | SPA_IO_OUT | SPA_IO_HUP | SPA_IO_ERR,
12161251
do_close, on_remote_data, impl);
12171252
if (impl->source == NULL)
12181253
return -errno;
12191254

1255+
/* Add connection handshake timeout */
1256+
value.tv_sec = CONNECT_TIMEOUT_MS / 1000;
1257+
value.tv_nsec = (CONNECT_TIMEOUT_MS % 1000) * 1000000;
1258+
impl->timer = pw_loop_add_timer(loop, on_connect_timeout, impl);
1259+
if (impl->timer != NULL)
1260+
pw_loop_update_timer(loop, impl->timer, &value, NULL, true);
1261+
12201262
return 0;
12211263
}
12221264

@@ -1230,6 +1272,11 @@ static void impl_disconnect(struct pw_protocol_client *client)
12301272
pw_loop_destroy_source(impl->context->main_loop, impl->source);
12311273
impl->source = NULL;
12321274

1275+
if (impl->timer) {
1276+
pw_loop_destroy_source(impl->context->main_loop, impl->timer);
1277+
impl->timer = NULL;
1278+
}
1279+
12331280
pw_protocol_native_connection_set_fd(impl->connection, -1);
12341281
}
12351282

0 commit comments

Comments
 (0)