May 14, 2020

systemd-run + fish shell = poor man's jail for untrusted processes

One of the well-documented effects of Coronavirus is the move to video conferencing from home. This means more Zoom, Google Meet/Hangouts, Jitsi, etc. - programs I don't necessarily trust to have full access to my home directory. Using the fish shell and systemd-run, I made the following utility to ensure certain processes don't see my $HOME directory while still having the convenience of running them as myself without too much hassle. You can find the updated source here:

#! /usr/bin/env fish

################################################################################
# Poor-man's "jail" for untrusted processes, or at least untrusted to read/write
# to $HOME. Uses systemd-run to run an untrusted command such that $HOME is
# mapped to $HOME/.local/share/jail/<program name> instead, thus ensuring that
# program can't read/write your real $HOME directory.
#
# Usage: make sure this file is executable, then from somewhere in your $PATH
#        but before the actual path to the program, symlink names of the
#        programs you want to "jail" to this file.
#
# Example for Google Chrome, assuming $HOME/bin is in your PATH before /usr/bin:
#
#   $ cd $HOME/bin
#   $ ln -s <path to this file> google-chrome-stable
#   $ google-chrome-stable # Runs Google Chrome with $HOME really set to
#                            $HOME/.local/share/jail/google-chrome-stable
#
################################################################################

# Default arguments to systemd-run
set systemd_run_args --uid=(id -u) --gid=(id -g) --wait

# Connect stdin if available
isatty
  and set -a systemd_run_args --pty

# Loop through all exported variables for use with systemd-run -E
for var in (set -xn)
    test (count $$var) -gt 1
      # Array/PATH variable, flatten and join with :
      and set -a systemd_run_args -E $var=(string join : $$var)
      # Single value variable
      or set -a systemd_run_args -E $var=$$var
end

set program_to_run (basename (status filename))
set path_to_me (dirname (status filename))

# Find the real executable to run
for dir in $PATH
    test $dir = $path_to_me
      and continue

    test -x $dir/$program_to_run
      and set exe $dir/$program_to_run
      and break
end

set -q exe
  or begin
       echo Unable to find $program_to_run in $PATH
       exit 1
     end

# Create our jail if it doesn't exist
set jail $HOME/.local/share/jail/$program_to_run
test -d $jail
  or mkdir -p $jail

test -d $jail
  or begin
       echo Unable to create/use jail at $jail
       exit 1
     end

set -a systemd_run_args -p BindPaths=$jail:$HOME

# If we are below $HOME when invoked use $HOME as the working directory,
# otherwise use the directory where we currently are
if string match --regex "$HOME/?" (pwd -P) >/dev/null
  set -a systemd_run_args --working-directory=$HOME
else
  set -a systemd_run_args --same-dir
end

# Must be root to bind-mount, use sudo if necessary
if test (id -u) -ne 0
  exec sudo systemd-run $systemd_run_args $exe $argv
else
  exec systemd-run $systemd_run_args $exe $argv
end