Skip to content
Open
5 changes: 4 additions & 1 deletion policy/modules/apps/pipewire.fc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# PipeWire daemon executable.
/usr/bin/pipewire -- gen_context(system_u:object_r:pipewire_exec_t,s0)
/usr/bin/wpctl -- gen_context(system_u:object_r:pipewire_exec_t,s0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not belong here as its part of wireplumber and even if then pipewire_client_exec_t would probably be much more appropriate than pipewire_exec_t.


# pw-cat is a standalone client utility (plays/captures audio via a running
# PipeWire daemon); it runs in pipewire_client_t, not pipewire_t.
Expand All @@ -11,7 +12,9 @@
/run/pipewire(/.*)? gen_context(system_u:object_r:pipewire_runtime_t,s0)

# User-session runtime sockets live under $XDG_RUNTIME_DIR/pipewire and are
# labelled at runtime via userdom_user_runtime_filetrans; no static entry needed.
# labelled at runtime via userdom_user_runtime_filetrans
# Static entry added for restorecon
/run/user/%{USERID}/pipewire-.* gen_context(system_u:object_r:pipewire_runtime_t,s0)

# Persistent system-service state.
/var/lib/pipewire(/.*)? gen_context(system_u:object_r:pipewire_var_lib_t,s0)
Expand Down
43 changes: 43 additions & 0 deletions policy/modules/apps/pipewire.if
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ template(`pipewire_role',`
allow $2 pipewire_runtime_t:dir { manage_dir_perms relabel_dir_perms };
allow $2 pipewire_runtime_t:sock_file { manage_sock_file_perms relabel_sock_file_perms };

pipewire_client_domain($2)

# Allow the user domain to manage shared memory files created by the daemon.
pipewire_mmap_rw_tmpfs_files($2)
allow $2 pipewire_tmpfs_t:file relabel_file_perms;
Expand Down Expand Up @@ -165,6 +167,27 @@ interface(`pipewire_use_fds',`
allow $1 pipewire_t:fd use;
')

########################################
## <summary>
## Send and receive messages from
## pipewire over dbus.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
#
interface(`pipewire_dbus_chat',`
gen_require(`
type pipewire_t;
class dbus send_msg;
')

allow $1 pipewire_t:dbus send_msg;
allow pipewire_t $1:dbus send_msg;
')

########################################
## <summary>
## Use file descriptors inherited from PipeWire clients.
Expand Down Expand Up @@ -223,3 +246,23 @@ interface(`pipewire_read_home_files',`
read_files_pattern($1, pipewire_home_t, pipewire_home_t)
read_lnk_files_pattern($1, pipewire_home_t, pipewire_home_t)
')

########################################
## <summary>
## Connect to pipewire and manage
## pipewire config data.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
#
interface(`pipewire_client_domain',`
gen_require(`
attribute pipewire_client;
')

typeattribute $1 pipewire_client;
dbus_all_session_bus_client($1)
')
57 changes: 43 additions & 14 deletions policy/modules/apps/pipewire.te
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ policy_module(pipewire)

attribute_role pipewire_roles;

attribute pipewire_client;

#
# pipewire_t — the PipeWire multimedia daemon.
#
Expand Down Expand Up @@ -54,7 +56,7 @@ userdom_user_tmpfs_file(pipewire_tmpfs_t)
#

allow pipewire_t self:capability sys_nice;
allow pipewire_t self:process { getsched setsched signal sigchld };
allow pipewire_t self:process { getsched setsched signal sigchld setrlimit };
allow pipewire_t self:fifo_file rw_fifo_file_perms;
allow pipewire_t self:unix_stream_socket { create_stream_socket_perms connectto };

Expand All @@ -72,6 +74,10 @@ fs_tmpfs_filetrans(pipewire_t, pipewire_tmpfs_t, file)
dev_read_sound(pipewire_t)
dev_write_sound(pipewire_t)

# Video hardware access.
dev_read_video_dev(pipewire_t)
dev_write_video_dev(pipewire_t)

# Device enumeration via sysfs (discovering ALSA/USB audio devices at startup).
dev_read_sysfs(pipewire_t)

Expand All @@ -81,6 +87,7 @@ domain_dontaudit_read_all_domains_state(pipewire_t)

# Read /proc state for scheduling and memory info.
kernel_read_system_state(pipewire_t)
kernel_request_load_module(pipewire_t)

files_read_etc_files(pipewire_t)
files_read_usr_files(pipewire_t)
Expand All @@ -90,17 +97,21 @@ logging_send_syslog_msg(pipewire_t)

miscfiles_read_localization(pipewire_t)

userdom_use_user_terminals(pipewire_t)

# NSS/name-service lookups to resolve user names for session ownership checks.
auth_use_nsswitch(pipewire_t)

# ALSA configuration files (/etc/alsa/*, ~/.asoundrc).
optional_policy(`
alsa_read_config(pipewire_t)
alsa_read_home_files(pipewire_t)
alsa_read_lib(pipewire_t)
')

# PulseAudio compatibility layer (pipewire-pulse connects to PulseAudio clients).
optional_policy(`
pulseaudio_stream_connect(pipewire_t)
pulseaudio_provide_server(pipewire_t)
')

# Real-time scheduling priority via rtkit-daemon.
Expand All @@ -113,6 +124,10 @@ optional_policy(`
systemd_read_journal_files(pipewire_t)
')

optional_policy(`
dbus_system_bus_client(pipewire_t)
')

########################################
#
# pipewire_t — system-service-only policy
Expand Down Expand Up @@ -154,20 +169,42 @@ ifndef(`pipewire_system_service',`
allow pipewire_t pipewire_home_t:file mmap_manage_file_perms;
manage_lnk_files_pattern(pipewire_t, pipewire_home_t, pipewire_home_t)

# Runtime socket directory transition under $XDG_RUNTIME_DIR/pipewire.
userdom_user_runtime_filetrans(pipewire_t, pipewire_runtime_t, dir, "pipewire")
# Runtime transition under $XDG_RUNTIME_DIR
userdom_user_runtime_filetrans(pipewire_t, pipewire_runtime_t, { dir file sock_file })

# Session D-Bus (register org.PipeWire.* on the per-user session bus).
optional_policy(`
dbus_all_session_bus_client(pipewire_t)
dbus_connect_all_session_bus(pipewire_t)
dbus_write_session_runtime_socket(pipewire_t)
')

# Allow systemd --user to start the user-service instance.
optional_policy(`
systemd_user_daemon_domain(user, pipewire_exec_t, pipewire_t)
systemd_user_daemon_domain(staff, pipewire_exec_t, pipewire_t)
')

optional_policy(`
xdg_search_config_dirs(pipewire_t)
')
')

########################################
#
# pipewire_client domain
#
# domain that captures requirements to use the pipewire service
#

pipewire_stream_connect(pipewire_client)
pipewire_rw_stream_sockets(pipewire_client)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is questionable. AVC denial of the event that prompted you to add this would be interesting to interpret. Could just be a leak unrelated to pipewire_client.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also noticed that you allow pw clients to dbus chat with pw. I actually don't have these rules (and see these accesses) in my policy but pw indeed maintains a name on the user dbus ... I wonder whether that pipewire_rw_stream_sockets(pipewire_client) might be due to that socket being passed through dbus in your scenario. I am just comparing your rules to what I have as per my sesearch analysis.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o wait ...

root@nimbus:~# sesearch --dontaudit -t pipewire.subj -dt
dontaudit pautils.subj pipewire.subj:unix_stream_socket { append bind connect getattr getopt ioctl read setopt shutdown write };
dontaudit pipewire.subj pipewire.subj:capability net_admin;

So yes I am pretty sure this is related to pipewire-pulse one way or another. Should probably be dontaudited.

pipewire_mmap_rw_tmpfs_files(pipewire_client)

@kcinimod-defensec kcinimod-defensec Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably ideally be pipewire_mmap_rw_inherited_tmpfs_files(pipewire_client)

With these memfd/shm aspects excluding open can be a real win.

Here are my pipewire_client direct rules (in case youre interested)

root@nimbus:~# sesearch -A -s pipewire.client.typeattr -ds
allow pipewire.client.typeattr pipewire.conf.file:dir { getattr ioctl lock open read search };
allow pipewire.client.typeattr pipewire.conf.file:file { getattr ioctl lock map open read };
allow pipewire.client.typeattr pipewire.data.file:dir { getattr ioctl lock open read search };
allow pipewire.client.typeattr pipewire.data.file:file { getattr ioctl lock map open read };
allow pipewire.client.typeattr pipewire.home.conf.file:dir { getattr ioctl lock open read search };
allow pipewire.client.typeattr pipewire.home.conf.file:file { getattr ioctl lock map open read };
allow pipewire.client.typeattr pipewire.memfd_file:memfd_file { append getattr ioctl lock map read write };
allow pipewire.client.typeattr pipewire.run.file:sock_file { append getattr ioctl lock open write };
allow pipewire.client.typeattr pipewire.subj:fd use;
allow pipewire.client.typeattr pipewire.subj:unix_stream_socket connectto;
allow pipewire.client.typeattr pipewire.tmpfs.file:file { append getattr ioctl lock map read write };
root@nimbus:~# sesearch -A -t pipewire.client.typeattr -dt
allow pipewire.subj pipewire.client.typeattr:dir { getattr ioctl lock open read search };
allow pipewire.subj pipewire.client.typeattr:fd use;
allow pipewire.subj pipewire.client.typeattr:file { getattr ioctl lock open read };
allow pipewire.subj pipewire.client.typeattr:lnk_file { getattr lock read };
root@nimbus:~# sesearch -A -t pipewire.client.memfd.typeattr -dt
allow pipewire.subj pipewire.client.memfd.typeattr:memfd_file { append getattr ioctl lock read write };
root@nimbus:~# sesearch -A -t pipewire.client.tmpfs.typeattr -dt
allow pipewire.subj pipewire.client.tmpfs.typeattr:file { append getattr ioctl lock read write };

The pipewire.client.tmpfs.typeattr is for compatibility with pre-mem_fd.

pipewire_use_fds(pipewire_client)
allow pipewire_t pipewire_client:fd use;

optional_policy(`
pipewire_dbus_chat(pipewire_client)

@kcinimod-defensec kcinimod-defensec Jun 29, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is pipewire-pulse/org.pulseaudio.Server. Maybe I am overlooking something. Would be nice if there is a way to separate the pulseaudio compat rules because pulseaudio is on its way out. I have a sepearate module for pulseaudio compat specific rules. So that I can just delete that when I decide to get rid of my pipewire-pulse support.

')

########################################
Expand All @@ -192,23 +229,15 @@ kernel_read_system_state(pipewire_client_t)
miscfiles_read_localization(pipewire_client_t)

# Connect to the PipeWire daemon socket and map its shared memory buffers.
pipewire_stream_connect(pipewire_client_t)
pipewire_rw_stream_sockets(pipewire_client_t)
pipewire_mmap_rw_tmpfs_files(pipewire_client_t)
pipewire_use_fds(pipewire_client_t)
pipewire_use_client_fds(pipewire_t)
pipewire_client_domain(pipewire_client_t)

# Read media files from the user's home, and allow capture tools such as
# pw-record to manage user-selected output files in /tmp.
userdom_read_user_home_content_files(pipewire_client_t)
userdom_manage_user_tmp_files(pipewire_client_t)
userdom_tmp_filetrans_user_tmp(pipewire_client_t, file)
userdom_use_user_terminals(pipewire_client_t)

optional_policy(`
alsa_read_config(pipewire_client_t)
')

optional_policy(`
dbus_all_session_bus_client(pipewire_client_t)
dbus_connect_all_session_bus(pipewire_client_t)
')
27 changes: 27 additions & 0 deletions policy/modules/apps/pulseaudio.if
Original file line number Diff line number Diff line change
Expand Up @@ -446,3 +446,30 @@ interface(`pulseaudio_rw_tmpfs_files',`
fs_search_tmpfs($1)
rw_files_pattern($1, pulseaudio_tmpfs_t, pulseaudio_tmpfs_t)
')

#######################################
## <summary>
## Allow specified domain to act as a pulseaudio server.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed/allowing access.
## </summary>
## </param>
#
interface(`pulseaudio_provide_server',`
gen_require(`
attribute pulseaudio_client;
type pulseaudio_tmp_t;
')

manage_dirs_pattern($1, pulseaudio_tmp_t, pulseaudio_tmp_t)
manage_files_pattern($1, pulseaudio_tmp_t, pulseaudio_tmp_t)
allow $1 pulseaudio_tmp_t:file map;
manage_sock_files_pattern($1, pulseaudio_tmp_t, pulseaudio_tmp_t)
userdom_user_runtime_filetrans($1, pulseaudio_tmp_t, dir, "pulse")
pulseaudio_stream_connect($1)
ps_process_pattern($1, pulseaudio_client)

allow pulseaudio_client $1:fd use;
')
10 changes: 10 additions & 0 deletions policy/modules/apps/wireplumber.fc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
HOME_DIR/\.config/wireplumber(/.*)? gen_context(system_u:object_r:wireplumber_home_t,s0)
HOME_DIR/\.local/share/wireplumber(/.*)? gen_context(system_u:object_r:wireplumber_home_t,s0)
HOME_DIR/\.local/state/wireplumber(/.*)? gen_context(system_u:object_r:wireplumber_home_t,s0)

/run/wireplumber(/.*)? gen_context(system_u:object_r:wireplumber_runtime_t,s0)
/run/user/%{USERID}/wireplumber(/.*)? gen_context(system_u:object_r:wireplumber_runtime_t,s0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add support for wireplumber_runtime_t sockets? Who calls wireplumber_stream_connect() and why?


/usr/bin/wireplumber -- gen_context(system_u:object_r:wireplumber_exec_t,s0)

/var/lib/wireplumber(/.*)? gen_context(system_u:object_r:wireplumber_var_lib_t,s0)
91 changes: 91 additions & 0 deletions policy/modules/apps/wireplumber.if
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
## <summary>Policy for WirePlumber, the PipeWire session manager.</summary>

########################################
## <summary>
## Connect to WirePlumber over a unix stream socket.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
#
interface(`wireplumber_stream_connect',`
gen_require(`
type wireplumber_t, wireplumber_runtime_t;
')

files_search_runtime($1)
stream_connect_pattern($1, wireplumber_runtime_t, wireplumber_runtime_t, wireplumber_t)
')

########################################
## <summary>
## Send and receive messages from
## wireplumber over dbus.
## </summary>
## <param name="domain">
## <summary>
## Domain allowed access.
## </summary>
## </param>
#
interface(`wireplumber_dbus_chat',`
gen_require(`
type wireplumber_t;
class dbus send_msg;
')

allow $1 wireplumber_t:dbus send_msg;
allow wireplumber_t $1:dbus send_msg;
')

########################################
## <summary>
## Role access for WirePlumber in user-service mode.
## </summary>
## <param name="role_prefix">
## <summary>
## The prefix of the user role (e.g., "user" for user_r).
## </summary>
## </param>
## <param name="user_domain">
## <summary>
## User domain for the role (e.g., user_t).
## </summary>
## </param>
## <param name="user_exec_domain">
## <summary>
## User exec domain for execute and transition access
## (e.g., user_application_exec_domain).
## </summary>
## </param>
## <param name="role">
## <summary>
## Role allowed access (e.g., user_r).
## </summary>
## </param>
#
interface(`wireplumber_role',`
gen_require(`
type wireplumber_t, wireplumber_exec_t;
type wireplumber_tmpfs_t, wireplumber_runtime_t, wireplumber_home_t;
attribute_role wireplumber_roles;
')

wireplumber_dbus_chat($2)

roleattribute $4 wireplumber_roles;
ifndef(`wireplumber_system_service',`
manage_files_pattern($2, wireplumber_tmpfs_t, wireplumber_tmpfs_t)
manage_sock_files_pattern($2, wireplumber_tmpfs_t, wireplumber_tmpfs_t)
manage_dirs_pattern($2, wireplumber_tmpfs_t, wireplumber_tmpfs_t)
manage_files_pattern($2, wireplumber_runtime_t, wireplumber_runtime_t)
manage_sock_files_pattern($2, wireplumber_runtime_t, wireplumber_runtime_t)
manage_dirs_pattern($2, wireplumber_runtime_t, wireplumber_runtime_t)
manage_files_pattern($2, wireplumber_home_t, wireplumber_home_t)
manage_dirs_pattern($2, wireplumber_home_t, wireplumber_home_t)
systemd_user_daemon_domain($1, wireplumber_exec_t, wireplumber_t)
domtrans_pattern($3, wireplumber_exec_t, wireplumber_t)
')
')
Loading
Loading