File: //bin/crontab
#!/usr/bin/bash
##CageFS proxyexec wrapper - ver 18
if [[ $EUID -eq 0 ]]; then
echo 'Cannot be run as root'
exit 1
fi
# POSIX single-quote escaping for values embedded in the ssh remote
# command. Unlike `printf %q`, single-quoted output re-parses correctly
# under any POSIX shell (the origin login shell need not be bash) and is
# lossless for arbitrary bytes. Each embedded ' becomes the '\'' sequence.
sq() {
local s=${1//\'/\'\\\'\'}
printf "'%s'" "$s"
}
USR=`/usr/bin/whoami`
CWD=`pwd`
TOKEN=`/bin/cat /var/.cagefs/.cagefs.token`
# It's user's tmp directory and write to it is secure procedure
# because this script is running only under usual user
LOCKFILE=/tmp/.crontab.lock
# automatically obtain next available fd
# previous strategy with `ulimit -n` failed
# in environment where limit is very high (e.g. 1073741816)
exec {FD}>$LOCKFILE
# Sibling-wrapper hardening of the same command-injection vector named
# in cagefs.proxy.program for F-05 / CLOS-4596: caller-controlled $CWD
# (`pwd` — attacker controls via mkdir+cd inside the cage) and $USR
# are embedded into a command that runs under `eval` (needed for the
# `$FD> $LOCKFILE` dynamic-fd redirection). Single-quote each value
# with sq() so metacharacters cannot be re-interpreted. The number of
# quoting passes must match the number of shell re-parses the value
# crosses, which depends on the mode. $TOKEN is a bearer credential and
# is intentionally NOT embedded in the shell command line — see F-21 /
# CLOS-5408 handling below.
if [[ -e /var/.cagefs/origin ]]; then
ORIGIN=`/bin/cat /var/.cagefs/origin`
# F-45 / CLOS-5432: /var/.cagefs/origin is admin-provisioned under
# a root-owned parent, but its contents flow through three local
# `eval` re-parses below and ultimately land as ssh(1)'s destination
# argv element. CLOS-4596 sq()-hardened USR/CWD/TOKEN in the same
# block but overlooked ORIGIN. Any operator/toolchain mishap
# (whitespace, `; ...`, backticks, `$(...)`, or a leading
# `-oProxyCommand=...`) would either inject a local command via
# eval or an ssh option via argv parsing — shell-quoting alone
# cannot neutralise a leading `-`. Allow-list to hostname / IPv4 /
# IPv6-literal characters and refuse a leading `-`.
case "$ORIGIN" in
""|-*|*[!A-Za-z0-9.:-]*)
/usr/bin/logger -t cagefs.proxy.crontab \
"refusing to proxy: /var/.cagefs/origin has invalid contents"
exit 1
;;
esac
# Defense-in-depth: even after content validation, wrap ORIGIN
# with sq() so the local eval below cannot word-split on it.
# ORIGIN is ssh's destination argv element — the local eval
# re-parses the surrounding command string once, but ssh itself
# does not hand ORIGIN to the remote shell (that is the remote
# command payload's job). One sq() pass is therefore correct here;
# a two-pass wrap (like Q_USR / Q_CWD below, which DO reach the
# remote shell) would leave literal single quotes in the ssh
# destination and break normal name resolution.
Q_ORIGIN=$(sq "$ORIGIN")
REMOTE="/usr/bin/ssh -F /etc/ssh/cagefs-rexec_config $USR@$Q_ORIGIN"
# Distributed mode: the eval'd command is shipped by ssh and the
# origin login shell re-parses it, so values cross TWO re-parses
# (local eval, then remote shell) — quote twice.
Q_USR=$(sq "$(sq "$USR")")
Q_CWD=$(sq "$(sq "$CWD")")
# F-21 / CLOS-5408: keep the CAGEFS_TOKEN bearer secret out of the
# local ssh process's argv (readable via /proc/<ssh-pid>/cmdline
# under hidepid<2 or by a same-UID sibling regardless of hidepid).
# Ship the token as the first stdin line to ssh; the origin login
# shell reads it into CAGEFS_TOKEN via `read` and exports it, then
# invokes proxyexec, which authenticates from env as before. `IFS=`
# and `-r` protect against leading whitespace / backslash escapes,
# and the `&&` operators — quoted here so the local shell passes
# them to ssh as literal argv tokens — are re-interpreted as shell
# operators by the origin login shell after ssh space-joins argv.
STDIN_PREFIX="printf '%s\\n' \"\$TOKEN\";"
REMOTE_WRAP_PRE="$REMOTE IFS= read -r CAGEFS_TOKEN '&&' export CAGEFS_TOKEN '&&'"
else
REMOTE=""
# Local mode: the eval'd command runs here with no ssh hop, so values
# cross only ONE re-parse (the eval) — quote once. A second pass would
# survive the single eval as literal quote characters and corrupt the
# arguments proxyexec receives.
Q_USR=$(sq "$USR")
Q_CWD=$(sq "$CWD")
Q_TOKEN=$(sq "$TOKEN")
# Local mode is not vulnerable to the F-21 argv-leak — the env
# prefix `CAGEFS_TOKEN=…` applies to the immediately-following
# proxyexec exec, so the token lives in proxyexec's own environ,
# never in any argv on this host.
STDIN_PREFIX=""
REMOTE_WRAP_PRE="CAGEFS_TOKEN=$Q_TOKEN"
fi
eval "(
/usr/bin/flock -x -w 10 $FD || exit 1
{ $STDIN_PREFIX echo -n \"\"; } | $REMOTE_WRAP_PRE /usr/sbin/proxyexec -c cagefs.sock $Q_USR $Q_CWD CRONTAB_CHECK $$
) $FD> $LOCKFILE"
[ $? -ne 0 ] && exit 1
eval "(
/usr/bin/flock -x -w 10 $FD || exit 1
{ $STDIN_PREFIX echo -n \"\"; } | $REMOTE_WRAP_PRE /usr/sbin/proxyexec -c cagefs.sock $Q_USR $Q_CWD CRONTAB_LIST $$ 2>/dev/null |cat > /var/spool/cron/$USR
) $FD> $LOCKFILE"
/usr/bin/crontab.cagefs $@
eval "(
/usr/bin/flock -x -w 10 $FD || exit 1
if [ -e /var/spool/cron/$USR ]; then
{ $STDIN_PREFIX cat /var/spool/cron/$USR; } | $REMOTE_WRAP_PRE /usr/sbin/proxyexec -c cagefs.sock $Q_USR $Q_CWD CRONTAB_SAVE $$ 2>/dev/null
else
{ $STDIN_PREFIX echo -n \"\"; } | $REMOTE_WRAP_PRE /usr/sbin/proxyexec -c cagefs.sock $Q_USR $Q_CWD CRONTAB_SAVE $$ 2>/dev/null
fi
) $FD>$LOCKFILE"