From 52afec565b94d22aa35b7a0d27f02c9822d7e9d3 Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Wed, 29 Apr 2026 16:39:57 +0200 Subject: [PATCH] security: add total sample cache size limit in PulseAudio protocol There was no limit on the total size of the sample cache. A client could upload many samples to exhaust server memory. Add a configurable pulse.max-sample-cache property (default 64MB) to cap the total size of all cached samples. Co-Authored-By: Claude Opus 4.7 --- src/daemon/pipewire-pulse.conf.in | 1 + src/modules/module-protocol-pulse.c | 8 ++++++++ src/modules/module-protocol-pulse/defs.h | 1 + src/modules/module-protocol-pulse/internal.h | 1 + src/modules/module-protocol-pulse/pulse-server.c | 7 +++++++ 5 files changed, 18 insertions(+) diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 77dc8397e..bf4b93c77 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -120,6 +120,7 @@ pulse.properties = { #pulse.min.quantum = 256/48000 # 5.3ms #pulse.idle.timeout = 0 # don't pause after underruns #pulse.max-streams = 64 # max streams per client + #pulse.max-sample-cache = 67108864 # max total sample cache size (64MB) #pulse.default.format = F32 #pulse.default.position = [ FL FR ] } diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index 99becb6a3..1a52e390e 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -77,6 +77,7 @@ * #pulse.default.position = [ FL FR ] * #pulse.idle.timeout = 0 * #pulse.max-streams = 64 + * #pulse.max-sample-cache = 67108864 * } * * pulse.properties.rules = [ @@ -254,6 +255,13 @@ * * The maximum number of streams a single client can create. Default is 64. * + *\code{.unparsed} + * pulse.max-sample-cache = 67108864 + *\endcode + * + * The maximum total size in bytes of all sample cache entries. Default is + * 67108864 (64MB). + * * ## Command execution * * As part of the server startup sequence, a set of commands can be executed. diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index 92161d09b..87d7ee3f7 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -38,6 +38,7 @@ #define MAX_NAME 1024u #define SCACHE_ENTRY_SIZE_MAX (1024*1024*16) +#define MAX_SAMPLE_CACHE (1024u*1024*64) /* 64MB */ #define MAX_CLIENTS 64u #define MAX_STREAMS 64u diff --git a/src/modules/module-protocol-pulse/internal.h b/src/modules/module-protocol-pulse/internal.h index 5b3af80b7..9f9531e7e 100644 --- a/src/modules/module-protocol-pulse/internal.h +++ b/src/modules/module-protocol-pulse/internal.h @@ -37,6 +37,7 @@ struct defs { uint32_t quantum_limit; uint32_t idle_timeout; uint32_t max_streams; + uint32_t max_sample_cache; }; struct stats { diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index b3af6f862..b98bd13b1 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -2413,6 +2413,12 @@ static int do_finish_upload_stream(struct client *client, uint32_t command, uint channel, name); struct sample *old = find_sample(impl, SPA_ID_INVALID, name); + uint32_t new_length = stream->attr.maxlength; + uint32_t old_length = old != NULL ? old->length : 0; + if (impl->stat.sample_cache + new_length - old_length > impl->defs.max_sample_cache) { + res = -ENOSPC; + goto error; + } if (old == NULL || old->ref > 1) { sample = calloc(1, sizeof(*sample)); if (sample == NULL) @@ -5606,6 +5612,7 @@ static void load_defaults(struct defs *def, struct pw_properties *props) parse_position(props, "pulse.default.position", DEFAULT_POSITION, &def->channel_map); parse_uint32(props, "pulse.idle.timeout", DEFAULT_IDLE_TIMEOUT, &def->idle_timeout); parse_uint32(props, "pulse.max-streams", SPA_STRINGIFY(MAX_STREAMS), &def->max_streams); + parse_uint32(props, "pulse.max-sample-cache", SPA_STRINGIFY(MAX_SAMPLE_CACHE), &def->max_sample_cache); def->sample_spec.channels = def->channel_map.channels; def->quantum_limit = 8192; }