From dd05114e95de5679c0eb7d0b5a0a7da9445618cb Mon Sep 17 00:00:00 2001 From: Emir Beganovic Date: Sat, 10 Oct 2026 02:48:26 +0200 Subject: [PATCH 1/2] Find the caller of the Bash startup check the way /proc numbers it The check read /proc/$$/cmdline. arch-chroot runs its command in a new PID namespace beneath the outer /proc, where $$ names no process or someone else's, so every bash -p entrypoint was rejected there with status 126. The installer met this as soon as one was called at install time: install/config/thunderbolt-authorization.sh runs omarchy-thunderbolt-authorization-admin, and the edge ISO stopped in "Configuring system" with "/proc/219/cmdline: No such file or directory". The check runs in a child of the caller, so read the parent from that child's /proc/self/stat, which is given in the numbering of the /proc it is read through. Outside a PID namespace that is $$, as before. --- bin/omarchy-security-functions | 11 ++++-- test/shell.d/security-bash-startup-test.sh | 41 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100755 test/shell.d/security-bash-startup-test.sh diff --git a/bin/omarchy-security-functions b/bin/omarchy-security-functions index 890d4430..12ad8a78 100755 --- a/bin/omarchy-security-functions +++ b/bin/omarchy-security-functions @@ -10,13 +10,18 @@ fi omarchy_security_require_privileged_bash_startup() { [[ $- == *p* ]] || return 1 + # The caller is this check's parent, under the number this /proc gives it. + # arch-chroot runs the installer in a new PID namespace beneath the outer + # /proc, where $$ names no process, or someone else's. /usr/bin/env -i /usr/bin/bash -p -c ' - mapfile -d "" -t argv <"/proc/$1/cmdline" || exit 1 - executable=$(/usr/bin/readlink -e -- "/proc/$1/exe") || exit 1 + stat=$("$test_tmp/entrypoint" </dev/null; then + "${in_new_pid_namespace[@]}" /usr/bin/bash -c "for i in {1..50}; do /usr/bin/true; done; '$test_tmp/entrypoint'; exit" Date: Sat, 10 Oct 2026 02:52:51 +0200 Subject: [PATCH 2/2] Test the startup check from a subshell, with a decoy -p, and rejecting in a PID namespace The rejection inside the new PID namespace ran without privileged mode, so it never reached the caller lookup, and nothing would have caught a check that accepted -p anywhere in the arguments. --- test/shell.d/security-bash-startup-test.sh | 28 +++++++++++----------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/shell.d/security-bash-startup-test.sh b/test/shell.d/security-bash-startup-test.sh index 8ca8a324..f9c9d924 100755 --- a/test/shell.d/security-bash-startup-test.sh +++ b/test/shell.d/security-bash-startup-test.sh @@ -7,24 +7,24 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh" test_tmp=$(mktemp -d) trap 'rm -rf "$test_tmp"' EXIT -cat >"$test_tmp/entrypoint" <"$test_tmp/entrypoint" +# Privileged mode turned on after startup: the mode is set, the proof is not. +printf 'set -p\nsource "%s"\n%s\n' "$ROOT/bin/omarchy-security-functions" "$check" >"$test_tmp/late" chmod +x "$test_tmp/entrypoint" -"$test_tmp/entrypoint" || fail "a script started as bash -p passes the startup check" -( "$test_tmp/entrypoint" ) || fail "the startup check passes from a subshell" -pass "a script started as bash -p passes the startup check" +"$test_tmp/entrypoint" || fail "a script started as bash -p passes the startup check, from a subshell too" +pass "a script started as bash -p passes the startup check, from a subshell too" -if /usr/bin/bash -c "set -p; source '$ROOT/bin/omarchy-security-functions'; omarchy_security_require_privileged_bash_startup"; then - fail "bash that only turned -p on later is rejected" -fi +for arguments in "" "-p"; do + if /usr/bin/bash "$test_tmp/late" $arguments; then + fail "bash that only turned -p on later is rejected" "arguments: $arguments" + fi +done if /usr/bin/bash "$test_tmp/entrypoint"; then fail "a script not started with -p is rejected" fi -pass "bash not started with -p is rejected" +pass "bash not started with -p is rejected, whatever its other arguments" # arch-chroot runs the installer in a new PID namespace under the outer /proc, # so the caller's own PID is not the number /proc knows it by. @@ -32,8 +32,8 @@ in_new_pid_namespace=(unshare --user --map-root-user --fork --pid) if "${in_new_pid_namespace[@]}" /usr/bin/true 2>/dev/null; then "${in_new_pid_namespace[@]}" /usr/bin/bash -c "for i in {1..50}; do /usr/bin/true; done; '$test_tmp/entrypoint'; exit"