From 2e95ec737acdd5c59df8b9b802c5ec5652821d42 Mon Sep 17 00:00:00 2001 From: Taksh Date: Tue, 22 Sep 2026 19:01:17 +0530 Subject: [PATCH] Probe sudo non-interactively before update sleep inhibition. Bare sudo -v hangs under passwordless NOPASSWD (verifypw=all) and under omarchy-update's logging PTY; fall back to pkexec only when attended. --- bin/omarchy-update-stay-awake | 9 +- test/shell.d/update-lock-test.sh | 8 +- test/shell.d/update-stay-awake-sudo-test.sh | 112 ++++++++++++++++++++ 3 files changed, 122 insertions(+), 7 deletions(-) create mode 100755 test/shell.d/update-stay-awake-sudo-test.sh diff --git a/bin/omarchy-update-stay-awake b/bin/omarchy-update-stay-awake index 5fd9eb151b5..5a18f0d2910 100755 --- a/bin/omarchy-update-stay-awake +++ b/bin/omarchy-update-stay-awake @@ -91,10 +91,13 @@ start() { if omarchy-cmd-present systemd-inhibit; then if (( EUID != 0 )); then - if [[ -t 0 ]]; then - sudo -v + # Never gate on bare `sudo -v`: under Arch's default verifypw=all it + # prompts even when NOPASSWD is granted, and omarchy-update always has a + # PTY via `script`, so [[ -t 0 ]] cannot mean a human is present (#9066). + # Probe non-interactively; fall back to pkexec only when attended. + if sudo -n true 2>/dev/null; then inhibit_runner=(sudo) - else + elif [[ ${OMARCHY_UPDATE_UNATTENDED:-} != 1 ]]; then inhibit_runner=(pkexec) fi fi diff --git a/test/shell.d/update-lock-test.sh b/test/shell.d/update-lock-test.sh index 06a4d5bce9e..6c93bcf77a3 100644 --- a/test/shell.d/update-lock-test.sh +++ b/test/shell.d/update-lock-test.sh @@ -123,9 +123,9 @@ if (( EUID != 0 )); then terminal_inhibit_pid_file="$test_tmp/terminal-inhibit-pid" write_stub sudo ' printf "%s\n" "$*" >>"$SUDO_LOG" -if [[ $1 == "-v" ]]; then - exit 0 -fi +# Non-interactive probe (#9066); never require a bare -v that can hang. +[[ $1 != "-n" ]] || shift +[[ $1 != "true" ]] || exit 0 exec "$@"' write_stub pkexec 'touch "$PKEXEC_MARKER"; exec "$@"' @@ -146,7 +146,7 @@ SH SUDO_LOG="$sudo_log" PKEXEC_MARKER="$pkexec_marker" INHIBIT_PID_FILE="$terminal_inhibit_pid_file" \ run_with_lock_env script -qefc "$terminal_driver" /dev/null >/dev/null - grep -qx -- '-v' "$sudo_log" || fail "terminal sleep inhibition validates sudo in the foreground" + grep -qx -- '-n true' "$sudo_log" || fail "terminal sleep inhibition probes sudo without prompting" grep -q '^systemd-inhibit ' "$sudo_log" || fail "terminal sleep inhibition runs through sudo" [[ ! -e $pkexec_marker ]] || fail "terminal sleep inhibition does not use pkexec" run_with_lock_env "$ROOT/bin/omarchy-update-stay-awake" stop diff --git a/test/shell.d/update-stay-awake-sudo-test.sh b/test/shell.d/update-stay-awake-sudo-test.sh new file mode 100755 index 00000000000..4d7afdc9781 --- /dev/null +++ b/test/shell.d/update-stay-awake-sudo-test.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +# omarchy-update-stay-awake must never call bare `sudo -v` (hangs under +# passwordless NOPASSWD with verifypw=all, and under unattended update PTYs). + +set -euo pipefail + +source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" + +script="$ROOT/bin/omarchy-update-stay-awake" +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +stub_bin="$test_tmp/bin" +runtime_dir="$test_tmp/runtime" +home="$test_tmp/home" +sudo_log="$test_tmp/sudo.log" +pkexec_log="$test_tmp/pkexec.log" +inhibit_marker="$test_tmp/inhibit-ran" +mkdir -p "$stub_bin" "$runtime_dir" "$home/.local/state/omarchy/indicators" + +cat >"$stub_bin/omarchy-cmd-present" <<'SH' +#!/bin/bash +[[ $1 == "systemd-inhibit" ]] +SH + +cat >"$stub_bin/omarchy-toggle-idle" <<'SH' +#!/bin/bash +exit 0 +SH + +cat >"$stub_bin/systemd-inhibit" <"$stub_bin/sudo" <<'SH' +#!/bin/bash +printf 'sudo %s\n' "$*" >>"$SUDO_LOG" +# A bare -v would hang for passwd_timeout on real systems under verifypw=all. +if [[ $1 == "-v" ]]; then + echo "bare sudo -v must not be used" >&2 + exit 99 +fi +if [[ $1 == "-n" && $2 == "true" ]]; then + [[ ${SUDO_N_OK:-1} == 1 ]] + exit $? +fi +[[ $1 != "-n" ]] || shift +exec "$@" +SH + +cat >"$stub_bin/pkexec" <<'SH' +#!/bin/bash +printf 'pkexec %s\n' "$*" >>"$PKEXEC_LOG" +exec "$@" +SH + +chmod +x "$stub_bin"/* + +wait_for() { + local file="$1" + local attempt + + for (( attempt = 0; attempt < 100; attempt++ )); do + [[ -e $file ]] && return 0 + sleep 0.02 + done + return 1 +} + +run_start() { + : >"$sudo_log" + : >"$pkexec_log" + rm -f "$inhibit_marker" + rm -rf "$runtime_dir/omarchy-update-stay-awake" + env -u OMARCHY_UPDATE_UNATTENDED "$@" \ + PATH="$stub_bin:$PATH" \ + HOME="$home" \ + XDG_RUNTIME_DIR="$runtime_dir" \ + SUDO_LOG="$sudo_log" \ + PKEXEC_LOG="$pkexec_log" \ + bash "$script" start + # Inhibit is backgrounded; wait until the stub has been entered. + wait_for "$inhibit_marker" || fail "stay-awake starts systemd-inhibit" "$(cat "$sudo_log"; echo ---; cat "$pkexec_log")" +} + +# Passwordless / cached ticket: non-interactive probe succeeds → sudo inhibit. +run_start SUDO_N_OK=1 +grep -qF 'sudo -n true' "$sudo_log" || fail "probes sudo with -n true" "$(cat "$sudo_log")" +grep -qF 'sudo systemd-inhibit' "$sudo_log" || + fail "runs inhibit via sudo when probe succeeds" "$(cat "$sudo_log")" +[[ ! -s $pkexec_log ]] || fail "does not use pkexec when sudo -n works" "$(cat "$pkexec_log")" +! grep -qE 'sudo -v($| )' "$sudo_log" || fail "must never call bare sudo -v" "$(cat "$sudo_log")" +pass "passwordless sudo uses non-interactive probe then sudo inhibit" + +# No sudo ticket, unattended update: never block on pkexec / password prompt. +# Inhibit still runs as the user (empty runner) so sleep blocking degrades safely. +run_start SUDO_N_OK=0 OMARCHY_UPDATE_UNATTENDED=1 +grep -qF 'sudo -n true' "$sudo_log" || fail "unattended still probes sudo -n" "$(cat "$sudo_log")" +! grep -qF 'sudo systemd-inhibit' "$sudo_log" || + fail "unattended does not sudo inhibit without a ticket" "$(cat "$sudo_log")" +[[ ! -s $pkexec_log ]] || fail "unattended does not fall back to pkexec" "$(cat "$pkexec_log")" +pass "unattended update skips privileged inhibit instead of hanging" + +# No sudo ticket, attended: fall back to pkexec (existing non-tty path). +run_start SUDO_N_OK=0 +grep -qF 'pkexec systemd-inhibit' "$pkexec_log" || + fail "attended without sudo ticket uses pkexec" "$(cat "$pkexec_log"; echo ---; cat "$sudo_log")" +! grep -qE 'sudo -v($| )' "$sudo_log" || fail "attended path must not call bare sudo -v" "$(cat "$sudo_log")" +pass "attended without sudo ticket uses pkexec without sudo -v"