Keep inhibitor state in validated private directories and verify the recorded owner, PID, start time and launch token before signaling. Authenticate the held command before detaching and drop it back to the invoking user. Serialize launch and cancellation, identify the child before publishing its state, and preserve caller-owned idle choices. Cover cross-account fallback state, process identity, cancellation, retry, and update-lock handling with isolated regressions.
137 lines
4.7 KiB
Bash
137 lines
4.7 KiB
Bash
#!/bin/bash
|
|
|
|
# Test the real orchestration with fixed privileged paths redirected to harmless
|
|
# stand-ins. No host sudo, package transaction, namespace root, or exploit runs.
|
|
boundary_tmp=$(mktemp -d)
|
|
trap 'rm -rf "$boundary_tmp"' EXIT
|
|
export SUDO_TEST_ROOT="$boundary_tmp/omarchy"
|
|
export SUDO_TEST_LOG="$boundary_tmp/events"
|
|
export SUDO_TEST_CACHE="$boundary_tmp/cache"
|
|
export OMARCHY_PATH="$SUDO_TEST_ROOT"
|
|
export SUDO_TEST_HOME="$boundary_tmp/home"
|
|
mkdir -p "$SUDO_TEST_HOME"
|
|
mkdir -p "$SUDO_TEST_ROOT/bin" "$SUDO_TEST_ROOT/mock" "$SUDO_TEST_ROOT/default/omarchy/sudo-no-update"
|
|
: >"$SUDO_TEST_LOG"
|
|
|
|
copy_boundary_file() {
|
|
python3 - "$ROOT" "$SUDO_TEST_ROOT" "$1" <<'PY'
|
|
import sys
|
|
from pathlib import Path
|
|
source, target, name = map(Path,sys.argv[1:])
|
|
p=target/name
|
|
p.parent.mkdir(parents=True,exist_ok=True)
|
|
s=(source/name).read_text().replace('$HOME', '$SUDO_TEST_HOME')
|
|
for command in ['sudo','pkexec','pacman','omarchy-pkg-missing','systemd-inhibit','setpriv','snapper']:
|
|
s=s.replace('/usr/bin/'+command, str(target/'mock'/command))
|
|
s=s.replace('PATH=/usr/bin:/usr/sbin:/bin:/sbin', 'PATH="'+str(target/'bin')+':/usr/bin:/usr/sbin:/bin:/sbin"')
|
|
p.write_text(s)
|
|
p.chmod((source/name).stat().st_mode & 0o777)
|
|
PY
|
|
}
|
|
|
|
copy_boundary_file bin/omarchy-security-functions
|
|
copy_boundary_file bin/omarchy-update-pacman
|
|
copy_boundary_file default/omarchy/sudo-no-update/sudo
|
|
|
|
cat >"$SUDO_TEST_ROOT/mock/sudo" <<'STUB'
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
printf 'sudo' >>"$SUDO_TEST_LOG"
|
|
printf ' %q' "$@" >>"$SUDO_TEST_LOG"
|
|
printf '\n' >>"$SUDO_TEST_LOG"
|
|
if [[ ${1:-} == "-h" ]]; then
|
|
if [[ ${SUDO_TEST_UNSUPPORTED:-0} == "1" ]]; then
|
|
echo 'usage: sudo [-ABbEHknPS] command'
|
|
else
|
|
echo 'usage: sudo [-ABbEHkNnPS] command'
|
|
fi
|
|
exit 0
|
|
fi
|
|
if [[ ${1:-} == "-k" || ${1:-} == "-K" ]]; then
|
|
[[ ${SUDO_TEST_REVOKE_FAIL:-0} != "1" ]] || exit 1
|
|
/usr/bin/rm -f "$SUDO_TEST_CACHE"
|
|
exit 0
|
|
fi
|
|
if [[ ${1:-} == "-N" ]]; then
|
|
shift
|
|
else
|
|
touch "$SUDO_TEST_CACHE"
|
|
fi
|
|
[[ ${SUDO_TEST_SUDO_FAIL:-0} != "1" ]] || exit 1
|
|
background=0
|
|
while (( $# )); do
|
|
case "$1" in
|
|
-N|-n) shift ;;
|
|
-b) background=1; shift ;;
|
|
-v) exit 0 ;;
|
|
-u|--user) shift 2 ;;
|
|
--) shift; break ;;
|
|
*) break ;;
|
|
esac
|
|
done
|
|
(( $# )) || exit 0
|
|
if (( background )); then
|
|
"$@" &
|
|
else
|
|
"$@"
|
|
fi
|
|
STUB
|
|
chmod +x "$SUDO_TEST_ROOT/mock/sudo"
|
|
|
|
cat >"$SUDO_TEST_ROOT/bin/test-step" <<'STUB'
|
|
#!/bin/bash
|
|
set -euo pipefail
|
|
step=${0##*/}
|
|
printf 'step:%s %s\n' "$step" "$*" >>"$SUDO_TEST_LOG"
|
|
if [[ $step == "systemd-run" ]]; then
|
|
# omarchy-update-pacman registers the transaction as a PID 1 scope on booted
|
|
# hosts. Run the wrapped command in place so the pacman step still executes.
|
|
while (( $# )) && [[ $1 == -* ]]; do shift; done
|
|
exec "$@"
|
|
fi
|
|
if [[ $step == "omarchy-hook" || $step == "omarchy-update-mise" ]]; then
|
|
[[ ! -e $SUDO_TEST_CACHE ]] || exit 91
|
|
fi
|
|
if [[ -n ${SUDO_TEST_REMOVE_WRAPPER_STEP:-} && "$step $*" == $SUDO_TEST_REMOVE_WRAPPER_STEP ]]; then
|
|
# Model a package transaction replacing the running tree with a release
|
|
# that predates the wrapper.
|
|
/usr/bin/rm -f "$OMARCHY_PATH/default/omarchy/sudo-no-update/sudo"
|
|
fi
|
|
if [[ ${SUDO_TEST_FAIL_STEP:-} == "$step" ]]; then
|
|
# Model a misbehaving child leaving state behind, then failing. Cleanup must
|
|
# still revoke it. This never invokes real sudo or exercises a privilege flaw.
|
|
touch "$SUDO_TEST_CACHE"
|
|
exit 17
|
|
fi
|
|
if [[ ${SUDO_TEST_SIGNAL_STEP:-} == "$step" ]]; then
|
|
touch "$SUDO_TEST_CACHE"
|
|
kill -TERM "$PPID"
|
|
exit 0
|
|
fi
|
|
case "$step" in
|
|
omarchy-update-system-pkgs|omarchy-update-keyring|omarchy-snapshot)
|
|
sudo /usr/bin/true
|
|
;;
|
|
pacman) exit 0 ;;
|
|
yay)
|
|
[[ $* == *"--sudo $OMARCHY_PATH/default/omarchy/sudo-no-update/sudo"* ]] || exit 92
|
|
[[ $* == *"--sudoloop=false"* ]] || exit 93
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$SUDO_TEST_ROOT/bin/test-step"
|
|
for step in omarchy-update-lock omarchy-update-requires-free-space omarchy-update-confirm omarchy-update-pkg-prune omarchy-snapshot omarchy-update-stay-awake omarchy-update-dev omarchy-update-keyring omarchy-update-system-pkgs omarchy-migrate omarchy-hook omarchy-update-aur-pkgs omarchy-update-mise omarchy-update-orphan-pkgs omarchy-update-analyze-logs omarchy-update-status omarchy-update-restart omarchy-pkg-aur-accessible omarchy-notification-dismiss pacman systemd-run cp yay; do
|
|
ln -s test-step "$SUDO_TEST_ROOT/bin/$step"
|
|
done
|
|
ln -s ../bin/test-step "$SUDO_TEST_ROOT/mock/pacman"
|
|
|
|
reset_boundary() {
|
|
: >"$SUDO_TEST_LOG"
|
|
/usr/bin/rm -f "$SUDO_TEST_CACHE"
|
|
unset SUDO_TEST_FAIL_STEP SUDO_TEST_SIGNAL_STEP SUDO_TEST_SUDO_FAIL SUDO_TEST_REVOKE_FAIL SUDO_TEST_UNSUPPORTED SUDO_TEST_REMOVE_WRAPPER_STEP
|
|
}
|
|
assert_boundary_cold() {
|
|
[[ ! -e $SUDO_TEST_CACHE ]] || fail "$1 left cached authorization"
|
|
[[ $(tail -1 "$SUDO_TEST_LOG") == "sudo -k" ]] || fail "$1 did not revoke at exit" "$(<"$SUDO_TEST_LOG")"
|
|
}
|