I am sure that if you managed a Linux system for a while, you probably have dealt with Unix sockets—special files that act like sockets. You probably also run into permission issues when dealing with these socket files.

In this post, I’ll describe some methods of dealing with these permission issues, and a situation in which each might apply.

Method 1: Unix Groups

If your Unix socket only needs to be accessed by one client, then the easiest approach is probably to leverage the group part of Unix permissions. If the group of the Unix socket is set to a group that contains the user the client is running under, and the group is granted rw permission, then the client will be able to connect to the Unix socket. (Note that this assumes the client is able to access all parent directories.)

The most common situation that I use this method for is the connection between a reverse proxy server, such as nginx, and a web application server, such as uwsgi or a FastCGI server.

This is most easily done if the program supports creating sockets as root and configuring the permissions on the socket before dropping root access. For example, spawn-fcgi can be run as root and supports configuring the user (-U), group (-G), and mode (-M) of the socket before switching users. You can then set the group to nginx (if your nginx runs with this group) and mode to 660 and nginx will be able to access the socket.

uwsgi is similar, although the group of the socket has to be the same as the group to switch to after dropping privileges. This may pose a security issue if your application is complex, but most of the time you could simply run uwsgi --gid nginx and solve the problem.

Sometimes, the program itself doesn’t support this, and you may need to use external mechanisms to launch it with the correct group. Certain launchers may not support setting the group, and in such cases, the sg(1) command may prove helpful. Note that sg will prompt for a password unless the user it is running as is part of the group specified on the command line.

Method 2: Proxy Server

Sometimes, the service needs to be accessed by multiple clients and configuring the group ID may not be helpful. In such cases, it might be better to use another process to proxy the connection instead, running with the desired permissions.

To create socket /tmp/target.sock with user quantum, group example, and mode 660 which forwards to /tmp/source.sock can be done with this command:

socat UNIX-CONNECT:/tmp/source.sock UNIX-LISTEN:/tmp/target.sock,user=quantum,group=example,mode=660,fork

Since you can create multiple sockets that refer to the same resource, you can create as many sockets as you need, all with different permissions, as long as you run socat under a user that can connect to the socket.

Note that the fork option is important, or otherwise socat will just handle one connection before exiting.

Method 3: ACLs

Sometimes, socat doesn’t cut it. For example, you may be dealing with low latency things like audio, or you are handling many clients. In such a case, you want the exact same underlying socket to be available to multiple different clients all with different users and groups.

In such a case, you can use access control lists, which allow you to specify exactly what access each user and group has to a file. For example, to grant user quantum and group example access to /tmp/example.sock, you can use the setfacl command as follows:

setfacl -m u:quantum:rw /tmp/example.sock
setfacl -m g:example:rw /tmp/example.sock

The downside of this method is that you usually have to run a bunch of commands after the process creates the socket, which usually requires some sort of wait loop for the process to start or a race-prone call to sleep.

This also does nothing when you can’t change the path of the socket, and the directory’s permissions do not allow access by other users. Note that whether users will be able to access files in a directory at all is determined by the executable bit (x) on the directory. This is separate from being able to get the list of files, which is determined by the read bit (r).

Method 4: Directory Permissions and File Bind Mounts

And this brings us to the craziest scenario, which I ran into a while ago.

I was running a virtual machine on my Linux desktop, and I wanted to use QEMU’s JACK audio support to output audio to PipeWire (with the JACK drop-in support). I was using libvirt which ran the virtual machine under the libvirt-qemu user and thus was unable to access the PipeWire socket /run/user/1000/pipewire-0.

For security reasons, I do not wish to run QEMU under my user, and I absolutely cannot allow other users any access to /run/user/1000, which may allow them to attack things like my SSH agent.

Note that PipeWire creates the socket with mode 666 and all access control is done by the 700 mode on /run/user/1000.

So ideally, I would like an alternative path to the PipeWire socket that QEMU could access.

An obvious thing to try would be symbolic links, but that would never work because symlinks are resolved by the user, which then needs access to the destination.

So instead, I came up with a crazy approach: use a mount point. Normally, we associate mounting with directories, but we could in fact bind mount a single file. So, if we create /example which libvirt-qemu could access, bind mount /example/pipewire-0 to /run/user/1000/pipewire-0, and set the environment variable PIPEWIRE_RUNTIME_DIR to /example. Then, PipeWire should be able to access the socket despite running under a different user!

The obvious solution with root access is thus:

# mkdir /example
# chown libvirt-qemu: /example
# chmod 700 /example
# touch /example/pipewire-0
# mount --bind /run/user/1000/pipewire-0 /example/pipewire-0

However, I do not wish to run this as root. So instead, I created an entry in /etc/fstab allowing me to perform this mount as myself:

/run/user/1000/pipewire-0 /example/pipewire-0 none bind,rw,user,noauto 0 0

The important options here are user, which allows non-root users to perform the mount operation, and noauto, which tells the system to not mount this at boot. Then, as the user quantum, I would be allowed to mount /example/pipewire-0. Also note that the user quantum would need write access to /example, which can be granted with the group approach or with an ACL.

Then, after /run/user/1000/pipewire-0 is created, we simply have to run mount /example/pipewire-0.

Since /run/user/1000/pipewire-0 is created by systemd, I could use the following override unit to handle the mounting of /example/pipewire-0 automatically:

[Socket]
ExecStartPost=/bin/mount /example/pipewire-0
ExecStopPre=/bin/umount /example/pipewire-0

And this works perfectly, without any overhead created by socat (frequent pauses in audio resulting in popping noises), or creating any security holes (other than exposing PipeWire to the virtual machine user).