#!/bin/bash # Privileged sandbox launcher for left4me script overlays. # # Invoked via sudo by the web user with two arguments: # numeric overlay id; bind-mounts /var/lib/left4me/overlays/ # read-write at /overlay inside the sandbox. # absolute path to a bash file already written by the web app; # bind-mounted read-only at /script.sh inside the sandbox. # # The script runs as a transient systemd .service with the full hardening # surface: cgroup limits + walltime kill, NoNewPrivileges, ProtectSystem, # ProtectHome, kernel-tunable / -module / -log protection, namespace # restriction, address-family restriction, capability bounding (empty), # seccomp filter (@system-service @network-io), MemoryDenyWriteExecute, # LockPersonality, RestrictSUIDSGID. Network namespace is *not* restricted — # scripts must reach the public internet to download workshop / l4d2center # / cedapug content. PID namespace is shared with the host (no # PrivatePID= directive in systemd); host PIDs are visible via /proc but # not signal-able due to UID mismatch. set -euo pipefail # Self-wrap into PID 1's mount namespace before doing anything mount-related. # The web app's left4me-web.service has PrivateTmp=true, which gives it a # private mount namespace. When the worker invokes us via sudo, we inherit # that namespace; our `mount --bind` would land there. systemd-run below # spawns transient units in PID 1's namespace (where they don't see the # private bind), so the sandbox would bind onto an empty staging dir and # permission-deny on every write. The sentinel env var avoids an exec loop. if [[ "${L4D2_SANDBOX_IN_PID1_MNT_NS:-}" != "1" ]]; then exec env L4D2_SANDBOX_IN_PID1_MNT_NS=1 \ /usr/bin/nsenter --mount=/proc/1/ns/mnt -- "$0" "$@" fi [[ $# -eq 2 ]] || { echo "usage: $0