Memory Safety: Medium
fill_card_info() uses pi->n_props from port info for an alloca()
without bounds checking. A card object with many port properties can
similarly exhaust the stack.
Add MAX_ALLOCA_SIZE checks consistent with the existing pattern to
prevent stack overflow from large property counts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memory Safety: Medium
Several functions in the PulseAudio protocol implementation use alloca()
to allocate arrays of port_info, profile_info, or dict_item structs
based on counts derived from card parameters or client property lists.
These counts have no upper bounds, so a card object with a very large
number of parameters or a client sending many properties can cause
alloca() to exhaust the stack, resulting in a stack overflow crash.
Add a MAX_ALLOCA_SIZE (64KB) limit and check element counts before each
alloca() call. If the requested allocation exceeds the limit, the
function returns -ENOMEM instead of crashing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
In multiple cases the `flags` member of `spa_dict` is left unitialized,
so try to avoid that. For example in `fill_node_info_proplist()` it is
accessed in `spa_dict_lookup_item()`.
This also modifies `collect_props()` to not depend on a partially initialized
`dict` parameter.
Keep the flag dont_inhibit_auto_suspend around and use it do decide when
to send suspend messages to the client.
We don't always want to send suspend messages when the stream state changes
because that could happen because the stream was, for example, relinked.
The intention of the suspend message is mostly for monitor streams that
use the dont-inhibit flag and want to follow the suspend state of the
sink.
See #5273
Make a new library.filter-path for the filter-graph that will filter and
restrict the dlopen filenames (used for the LADSPA plugin only).
By default this is false and so filter-chain can load from absolute
paths without extra checks.
Enable the extra checks for the pulse LADSPA modules and the
audioconvert filter graphs because these allow loading LADSPA plugins
into other processes.
Fixes#5222
The aec_method parameter is interpolated into a SPA library path
as "aec/libspa-aec-%s". A client could use "../" sequences to
load arbitrary SPA plugins. Reject values containing ".." or "/".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A PulseAudio client can load this module with an arbitrarily complex
blocklist regex, causing catastrophic backtracking in regexec on
every new device. Cap the regex string at 1024 characters.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
spa_json_encode_string was called with sizeof(name)-1, which would
not write a null terminator on truncation. Use sizeof(name) and skip
sink names that don't fit in the buffer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
In the SPA_CHOICE_Enum case, values[index+1] was used to skip the
default value at index 0, but the bounds check only validated index,
not index+1. Move bounds checks into each case with the correct limit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The switch in message_get had no default case. An unrecognized tag byte
from a malicious client would skip the switch body without consuming
the va_arg parameter, desynchronizing all subsequent argument reads
and causing undefined behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
do_cork_stream, do_flush_trigger_prebuf_stream, and do_set_stream_name
did not check whether the stream had completed format negotiation.
Add create_tag guards matching the pattern in do_set_stream_buffer_attr.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A trailing backslash in a module argument string would cause the
escape handling to advance past the null terminator, reading one
byte out of bounds on the next loop iteration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
If a client sends UPDATE_PLAYBACK_STREAM_SAMPLE_RATE before format
negotiation completes, stream->ss.rate could be 0, causing a
floating-point division by zero. Add the same create_tag guard used
in do_set_stream_buffer_attr.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The create_tag guard added in a2de6c886 also rejected memblocks for
upload streams, which never clear create_tag. Upload streams allocate
their buffer immediately, so the NULL deref risk does not apply to
them. Exempt STREAM_TYPE_UPLOAD from the check.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
read_cvolume accepted channels=0, creating a degenerate zero-length
volume array that is passed to pw_stream_set_control and SPA pod
building. Reject zero channels alongside the existing CHANNELS_MAX
upper bound check.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A client can create a stream with invalid sample_spec (rate=0) via
format_info negotiation, then send SET_STREAM_BUFFER_ATTR before
negotiation completes. fix_playback_buffer_attr divides by ss.rate,
crashing the daemon. Reject buffer attr changes on streams that
have not completed format negotiation.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The client-provided rate was used without validation. A zero or
excessively large rate produces extreme correction values passed
to pw_stream_set_control. Reject rates that are zero or exceed
RATE_MAX.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A client can send memblock data to a playback stream channel before
format negotiation completes and the stream buffer is allocated,
causing a NULL pointer dereference crash. Reject memblock data for
streams that are still being created (create_tag != SPA_ID_INVALID).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
format_info_to_spec parses the format.channel_map property without
checking against CHANNELS_MAX (64) before writing to map->map[].
A client supplying more than 64 channel names overflows the stack-
allocated channel_map buffer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
File and Resource Handling: Medium
In on_connect(), if client_new() fails or pw_loop_add_io() fails, the
accepted client_fd is never closed. The error path only calls
client_free() which relies on pw_loop_destroy_source() to close the fd,
but if the source was never created, the fd leaks.
Fix by closing client_fd in the error path when it has not been
transferred to a loop source.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memory Safety: High
The read_arbitrary() bounds check used `m->offset + len > m->length`
where len is an attacker-controlled uint32_t read from the PulseAudio
protocol message. When m->offset is small and len is close to
UINT32_MAX, the addition wraps around to a small value, bypassing
the bounds check. This allows read_arbitrary() to return a pointer
within the message buffer but report an enormous length to the caller,
leading to out-of-bounds memory reads.
Fixed by rearranging the arithmetic to use subtraction:
`len > m->length - m->offset`, which cannot overflow since
m->offset <= m->length is maintained as an invariant.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memory Safety: High
The stream_control_info() callback copied control->n_values floats
into stream->volume.values without checking bounds. The source allows
up to MAX_VALUES (256) entries but the destination volume array is
only CHANNELS_MAX (64) entries, so a stream with more than 64 channel
volumes would overflow the buffer. Clamp n_values to CHANNELS_MAX
before the copy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Memory Safety: High
In ensure_size(), the check `m->length + size <= m->allocated` could
overflow when both m->length and size are large uint32_t values,
wrapping around to a small number and incorrectly passing the bounds
check. This could allow writing past the end of the allocated buffer.
Rewrite the check as `size <= m->allocated - m->length` which cannot
overflow since we already verified m->length <= m->allocated. Also add
an explicit overflow check for the new allocation size calculation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix path comparison in is_socket_unix() and don't unset LISTEN_FDS since
the function that uses it is called more than once and it was not unset
when sd_listen_fds() was used.
Fixes#5140
Socket activation uses sd_listen_fds from libsystemd, and can only be
compiled on systems with systemd.
This is an issue for Alpine / postmarketOS, where upstream has no
systemd package, but downstream depends on upstream's pipewire package
and wants to rely on socket activation. This also prevents using
socket-activation on other non-systemd distributions, including
non-Linux.
Implement equivalent functionality without a dependency on libsystemd.
Add 35 sec timeout for PLAY_SAMPLE streams to start streaming, similar
to what we do with normal streams, and fail playback if they don't
start.
This avoids pending sample playback using up resources indefinitely if
streams fail to start for some reason, e.g. session manager is not
linking them.
If we get EPROTO, we likely have missed on some messages from the
server, and our state is now out of sync.
It's likely we can't recover (e.g. if error is due to fd limit hit), so
just drop the server connection in this case, similarly as if we got
EPIPE.
WirePlumber recently added a mechanism to force mono mixdown on audio
outputs, which is a useful feature for accessibility. Let's also expose
that setting via libpulse for existing audio settings UIs to be able to
use.
The dont-inhibit-auto-suspend flag does not do anything when using
direct-on-input-idx (capturing from a stream) in pulseaudio, so also
make it do nothing on pulse-server.
See #4991
When a device profile changes (e.g., Bluetooth headset switching from
a2dp-sink to headset-head-unit), the active port information changes
but PulseAudio compatibility layer clients don't receive the expected
PA_SUBSCRIPTION_EVENT_SOURCE or PA_SUBSCRIPTION_EVENT_SINK change events.
Root cause:
The collect_device_info() function updates the active_port index from
SPA_PARAM_Route parameters, but doesn't update the corresponding
active_port_name field. When update_device_info() uses memcmp() to
detect changes in the device_info structure, it compares the entire
structure including active_port_name. If the pointer value doesn't
change (even though the actual port changed), no change is detected,
and the change_mask flag (PW_MANAGER_OBJECT_FLAG_SOURCE/SINK) is not
set, preventing subscription events from being sent.
Solution:
After setting active_port in collect_device_info(), look up the
corresponding port name from SPA_PARAM_EnumRoute parameters by
matching both the port index and direction. Initialize active_port_name
to NULL at the start to ensure it's always recalculated.
This fix applies to all device types (Bluetooth, USB, PCI sound cards)
and all profile switching scenarios, ensuring applications using the
PulseAudio compatibility layer receive proper device change notifications.
Tested with:
- Bluetooth headset profile switching (a2dp-sink ↔ headset-head-unit)
- Applications subscribing to PA_SUBSCRIPTION_MASK_SOURCE/SINK events
- Verified no regression in audio initialization
Add a function that accepts the size of the position array when reading
the audio positions. This makes it possible to decouple the position
array size from SPA_AUDIO_MAX_CHANNELS.
Also use SPA_N_ELEMENTS to pass the number of array elements to
functions instead of a fixed constant. This makes it easier to change
the array size later to a different constant without having to patch up
all the places where the size is used.
Instead of using timerfd, use the context timer-queue to schedule
timeouts. This saves fds and removes some redundant code.
Make the rtp-source timeout and standby code a bit better by using
atomic operations.
When clients connect with IP, add the peer IP address to properties. We
might use this later to make a better stream node.name than a copy of the
client application name.