#!/usr/bin/env bash set -uo pipefail # Stand-in readings for M0-VM-USERNS. Runs as the account that owns a subuid # range (RANGE_USER, default test-0001, because the invoking user has none) in # that account's user manager and as a plain process group. # # candidates: A = swc-userns-stage (static, single-threaded) # B = util-linux unshare --map-users/--map-groups --setuid/--setgid # B2 = unshare --keep-caps then setpriv --keep-groups (keeps supplementary groups) # Every candidate maps inner 0..SLOT-1 and switches to inner id INNER (not 0): # pasta confined as pasta_t cannot write its own namespace's uid_map when it # starts as root of the outer namespace. # G = a Go process calling unshare(CLONE_NEWUSER) itself # # Usage: standin.sh OUT=${1:?outdir} BIN=${BIN:-/var/tmp/swc-userns-spike/bin} RANGE_USER=${RANGE_USER:-test-0001} RU_UID=$(id -u "$RANGE_USER") RU_RT=/run/user/$RU_UID BASE=$(awk -F: -v u="$RANGE_USER" '$1==u {print $2}' /etc/subuid) GBASE=$(awk -F: -v u="$RANGE_USER" '$1==u {print $2}' /etc/subgid) SPAN=$(awk -F: -v u="$RANGE_USER" '$1==u {print $3}' /etc/subuid) SLOT=${SLOT:-$(( SPAN / 2 ))} PASTA=${PASTA:-/usr/bin/pasta} INNER=${INNER:-1000} PROBES="/home/johnandersen777/.ssh/config /home/johnandersen777/.local/state/swc-tasks /etc/shadow" mkdir -p "$OUT" chmod 1777 "$OUT" ru() { sudo -n -u "$RANGE_USER" env XDG_RUNTIME_DIR="$RU_RT" DBUS_SESSION_BUS_ADDRESS="unix:path=$RU_RT/bus" "$@"; } now() { date -u +%Y-%m-%dT%H:%M:%S.%3NZ; } pasta_argv() { PA=("$PASTA" --config-net -q -f -a 172.30.0.0 -n 255.255.255.252 -g 172.30.0.1 --dns-forward 172.30.0.1 /bin/sh -c 'exec "$0" "$@"' "$BIN/standin" "$1" $PROBES) } stage_argv() { local cand=$1 k=$2 local ub=$(( BASE + k * SLOT )) gb=$(( GBASE + k * SLOT )) case "$cand" in A) echo "$BIN/swc-userns-stage" "$ub" "$gb" "$SLOT" "$INNER" ;; B) echo /usr/bin/unshare --user --map-users="0:$ub:$SLOT" --map-groups="0:$gb:$SLOT" --setuid "$INNER" --setgid "$INNER" -- ;; B2) echo /usr/bin/unshare --user --map-users="0:$ub:$SLOT" --map-groups="0:$gb:$SLOT" --keep-caps -- /usr/bin/setpriv --reuid "$INNER" --regid "$INNER" --keep-groups -- ;; esac } wait_ready() { local f=$1 for _ in $(seq 1 100); do grep -q '^ready=' "$f" 2>/dev/null && return 0 sleep 0.1 done return 1 } tree_of_cgroup() { local cg=$1 for p in $(cat "/sys/fs/cgroup$cg/cgroup.procs" 2>/dev/null); do ps -o pid=,ppid=,user:12=,comm= -p "$p" done } unit_run() { local cand=$1 k=$2 name=socialweb-vm-spike-$1-$2 local log=$OUT/unit-$cand-$k.txt rd=$OUT/unit-$cand-$k.out rm -f "$rd" pasta_argv "$rd" local t0 t1 t0=$(date +%s%N) ru systemd-run --user --quiet --collect --unit "$name" -p Type=exec -p KillMode=control-group -p KillSignal=SIGKILL \ -p TimeoutStopSec=10 -p "StandardOutput=append:$rd.err" -p "StandardError=append:$rd.err" \ $(stage_argv "$cand" "$k") "${PA[@]}" wait_ready "$rd" t1=$(date +%s%N) { echo "== candidate $cand slot $k unit $name (systemd-user of $RANGE_USER) $(now)" echo "start_to_ready_ms=$(( (t1 - t0) / 1000000 ))" cat "$rd" local cg cg=$(ru systemctl --user show -p ControlGroup --value "$name") echo "cgroup=$cg" echo "-- unit processes (pid ppid user comm)" tree_of_cgroup "$cg" local vpid vpid=$(awk -F= '$1=="pid"{print $2}' "$rd") local hpid hpid=$(for p in $(cat "/sys/fs/cgroup$cg/cgroup.procs"); do [ "$(sudo -n readlink /proc/$p/exe 2>/dev/null)" = "$BIN/standin" ] && echo "$p"; done | head -1) echo "standin_host_pid=$hpid inner_pid=$vpid" echo "host_uid_of_standin=$(stat -c %u /proc/$hpid 2>/dev/null)" echo "-- tap in pasta's netns" sudo -n nsenter -t "$hpid" -n ip -d -br link show fctap0 2>&1 sudo -n nsenter -t "$hpid" -n ip -d link show fctap0 2>&1 | grep -o 'tun type tap.*' || true } >"$log" 2>&1 echo "$name" } unit_stop() { local name=$1 ru systemctl --user stop "$name" ru systemctl --user is-active "$name" 2>/dev/null || true } proc_run() { local cand=$1 k=$2 local log=$OUT/proc-$cand-$k.txt rd=$OUT/proc-$cand-$k.out rm -f "$rd" pasta_argv "$rd" local t0 t1 t0=$(date +%s%N) sudo -n -u "$RANGE_USER" setsid $(stage_argv "$cand" "$k") "${PA[@]}" >>"$rd.err" 2>&1 "$log" 2>&1 sudo -n kill -KILL -- "-$pg" } AVC_T0=$(date +%H:%M:%S) { echo "host=$(hostname) $(uname -r) selinux=$(getenforce) $(now)" echo "range_user=$RANGE_USER uid=$RU_UID subuid=$BASE:$SPAN subgid=$GBASE slot_size=$SLOT" echo "invoking_user=$(id -un) subuid_entry=$(grep -c "^$(id -un):" /etc/subuid)" ls -l /dev/kvm /dev/net/tun /usr/bin/newuidmap /usr/bin/newgidmap getcap /usr/bin/newuidmap /usr/bin/newgidmap /usr/bin/unshare --version $PASTA --version | head -1 echo "-- G: Go process unshare(CLONE_NEWUSER)" ru "$BIN/gounshare" echo "-- stage size $(stat -c %s "$BIN/swc-userns-stage") bytes sha256 $(sha256sum "$BIN/swc-userns-stage" | cut -c1-16)" } >"$OUT/host.txt" 2>&1 for cand in A B B2; do u0=$(unit_run "$cand" 0) u1=$(unit_run "$cand" 1) unit_stop "$u0"; unit_stop "$u1" proc_run "$cand" 0 done { echo "-- added latency: 200 x exec /bin/true, direct vs through each stage (as $RANGE_USER)" for cand in direct A B B2; do if [ "$cand" = direct ]; then argv=""; else argv=$(stage_argv "$cand" 0); fi ms=$(ru bash -c "t0=\$(date +%s%N); for i in \$(seq 200); do $argv /bin/true || exit 9; done; echo \$(( (\$(date +%s%N) - t0) / 200000 ))") echo "$cand per_exec_us=$ms" done } >"$OUT/latency.txt" 2>&1 { echo "-- files owned by the slot uids on this host (expect none)" for k in 0 1; do u=$(( BASE + k * SLOT + INNER )) n=$(sudo -n timeout 600 find / /home /var /tmp /opt -xdev -path "$OUT" -prune -o -uid "$u" -print 2>/dev/null | head -5) echo "uid $u: ${n:-none}" done echo "-- AVC denials since $AVC_T0" sudo -n ausearch -m avc,user_avc -ts "$AVC_T0" 2>&1 | grep -v '^----' | head -20 } >"$OUT/owners-avc.txt" 2>&1 echo "readings in $OUT"