From d54ae879b60049b5f3bb8805fc830cde78cb68a9 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Sat, 27 Jun 2026 07:22:47 +0000 Subject: [PATCH] Jailed terminal: per-session passwd so customers can't enumerate tenants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The jail's /etc/passwd listed every hosting username (no passwords — shadow isn't in the jail — but it enumerated tenants). The launcher now binds a per-session /etc/passwd|group (root + the current user only) inside each session's mount namespace, and the shared jail passwd no longer carries hosting users. Co-Authored-By: Claude Opus 4.8 --- deployment/ladill-jailsh | 16 ++++++++++------ deployment/setup-terminal-jail.sh | 10 +++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/deployment/ladill-jailsh b/deployment/ladill-jailsh index 7e324aa..19f409b 100755 --- a/deployment/ladill-jailsh +++ b/deployment/ladill-jailsh @@ -1,8 +1,7 @@ #!/bin/bash -# Ladill jailed shell for the hosting browser terminal. Confines a hosting -# account to /home/jail with ONLY its own home visible: a private mount namespace -# plus a per-session tmpfs /home that contains just this user's directory (so -# 'ls /home' shows their own folder and never other tenants). +# Ladill jailed shell for the hosting browser terminal. Per session (private mount +# ns): /home holds only this user's dir, and /etc/passwd|group expose only root + +# this user. The customer can never see other tenants, the host fs, or secrets. # Usage: ladill-jailsh [relpath] set -euo pipefail JAIL=/home/jail @@ -15,14 +14,19 @@ GID_=$(id -g "$U" 2>/dev/null || true) HOMEDIR=/home/$U [ -d "$HOMEDIR" ] || { echo 'no home directory'; exit 1; } case "$REL" in *..*) REL=/public_html;; esac -# re-exec into a private mount namespace so all mounts are per-session if [ -z "${LADILL_JAIL_NS:-}" ]; then exec env LADILL_JAIL_NS=1 unshare --mount --propagation private -- "$0" "$U" "$REL" fi -# Fresh, listable /home holding ONLY this user's directory (no tenant leak). +# /home with only this user's directory mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs "$JAIL/home" mkdir -p "$JAIL/home/$U" mount --bind "$HOMEDIR" "$JAIL/home/$U" +# passwd/group exposing only root + this user (no tenant enumeration) +mount -t tmpfs -o mode=0755,nosuid,nodev tmpfs "$JAIL/run" +{ echo 'root:x:0:0:root:/root:/bin/bash'; getent passwd "$U"; } > "$JAIL/run/passwd" +{ echo 'root:x:0:'; getent group "$U"; } > "$JAIL/run/group" +mount --bind "$JAIL/run/passwd" "$JAIL/etc/passwd" +mount --bind "$JAIL/run/group" "$JAIL/etc/group" export HOME="/home/$U" USER="$U" LOGNAME="$U" SHELL=/bin/bash PATH=/usr/bin:/bin export TERM="${TERM:-xterm-256color}" exec /usr/sbin/chroot --userspec="$UID_:$GID_" "$JAIL" /bin/bash -l -c "cd '/home/$U$REL' 2>/dev/null || cd ~; exec /bin/bash -l" diff --git a/deployment/setup-terminal-jail.sh b/deployment/setup-terminal-jail.sh index ee56789..ee88290 100755 --- a/deployment/setup-terminal-jail.sh +++ b/deployment/setup-terminal-jail.sh @@ -23,13 +23,9 @@ cp -rn /usr/share/terminfo/x "$JAIL/usr/share/terminfo/" 2>/dev/null || true install -d -o root -g root -m 711 "$JAIL/home" install -d -m 1777 "$JAIL/tmp" -echo "==> Syncing hosting users into the jail passwd/group (for name resolution)" -while IFS=: read -r name _ uid gid _ home shell; do - [ "$uid" -ge 1000 ] 2>/dev/null || continue - [ -d "/home/$name" ] || continue - grep -q "^$name:" "$JAIL/etc/passwd" || getent passwd "$name" >> "$JAIL/etc/passwd" - grep -q "^$name:" "$JAIL/etc/group" || getent group "$name" >> "$JAIL/etc/group" -done < <(getent passwd) +# NOTE: do NOT sync hosting users into the shared jail passwd — that would let +# any customer enumerate every tenant. ladill-jailsh injects a per-session +# passwd/group (root + the current user only) inside each session's namespace. echo "==> Installing the jailed-shell launcher + scoped sudoers" install -o root -g root -m 755 "$(dirname "$0")/ladill-jailsh" /usr/local/sbin/ladill-jailsh