Restricting access to your $HOME

From time to time I play some (mostly older) Steam games, which implies running closed-source binaries and hoping that they will not exploit the unrestricted access to your system. This has always been a little disturbing, but fortunately there are simple ways to decrease this risk, at least when it comes to your home directory.

As far as I know, unless we talk about more complicated options like AppArmor or SELinux, there are two general approaches to limit access to your personal files in Linux. Let’s explore them both.

Running the binary as another user

In the pre-Wayland times I had a working setup in which my web browser was running as a different user. In this case standard Unix file access permissions could be used to prevent that user from having access to my primary user files.

I tried using a similar approach this time as well, but found out that for Wayland/XWayland the setup became more involving. You can find more details in the discussion on the Gentoo forums, here I’ll just summarize the approach, assuming the new user is steam.

Wayland

To run Wayland applications, the new user must have access to the Wayland socket. Your primary user owns it, but you can use setfacl to share it with steam:

setfacl -m steam:r-x -- "$XDG_RUNTIME_DIR"
setfacl -m steam:rwx -- "$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY"

XWayland

To share access to the X server, you can configure mcookies:

touch ~/.Xauthority
xauth add "$DISPLAY" . "$(mcookie)"

You can list mcookies by running xauth list. Make sure to add the same mcookie to the .Xauthority of the steam user so that it can authenticate.

This also requires setting the -auth argument for Xwayland. For wlroots-based compositors you can set WLR_XWAYLAND to run a custom script that will start Xwayland with the -auth argument that points to the created .Xauthority file.

I found this setup a little bit too clumsy for my use case, so I decided to look into sandboxing.

Running the binary in a sandbox

The simplest option to run a sandboxed Steam would probably be Flatpak, however there are a few things I do not like about it, most of them are covered in the Flatpak Is Not the Future post. Specifically, Steam already uses sandboxing to replace the system libraries with its own. So, it makes little sense to use the heavy Flatpak environment, since the Steam runtime will re-mount almost everything anyway. It will not re-mount $HOME though, and this is what I want to fix.

As it turns out, there are at least two lightweight sandboxing options: firejail and bubblewrap. In fact, Steam comes with its own bubblewrap that it uses to prepare the runtime. I did not experiment with firejail much and chose bubblewrap as a simpler option that happened to be already installed on my system as a dependency of another package.

I came up with the following wrapper. Note that /mnt/sandbox is mounted as $HOME:

exec /usr/bin/bwrap \
  --unshare-pid \
  --ro-bind /{,} \
  --dev-bind /dev{,} \
  --proc /proc \
  --tmpfs /dev/shm \
  --tmpfs /tmp \
  --bind "$XDG_RUNTIME_DIR"{,} \
  --bind /mnt/sandbox "$HOME" \
  --ro-bind "$HOME/.local/bin"{,} \
  --ro-bind "$HOME/.Xauthority"{,} \
  --ro-bind /tmp/.X11-unix/X0{,} \
  "$@"

Compared to the previous approach, I obviously do not need to share anything, since the user is the same, and this simplifies the setup quite a bit.

In terms of performance, I did not notice any issues. I suspect that the sandbox may even be faster in some cases, since my home directory is encrypted and /mnt/sandbox is not, but I did not really measure that.