Keep Plymouth publishing working in dev mode
This commit is contained in:
+67
-29
@@ -76,6 +76,9 @@ run_root_transaction() {
|
||||
[[ -z $failure_reported ]] || return 0
|
||||
failure_reported=1
|
||||
printf "omarchy-plymouth-set: refusing to publish: %s failed validation\n" "$failure_context" >&2
|
||||
if [[ -n ${failure_hint:-} ]]; then
|
||||
printf "omarchy-plymouth-set: %s\n" "$failure_hint" >&2
|
||||
fi
|
||||
}
|
||||
trap report_failure ERR
|
||||
|
||||
@@ -95,14 +98,65 @@ run_root_transaction() {
|
||||
canonical_source_root=$(realpath -e -- "$source_root")
|
||||
[[ $canonical_source_root == "$source_root" ]]
|
||||
|
||||
# The one refusal a healthy machine can hit: omarchy dev link points
|
||||
# OMARCHY_PATH at a checkout the desktop user can rewrite, and executable
|
||||
# boot assets cannot come from there. Say so rather than failing mute.
|
||||
validate_trusted_directory() {
|
||||
local directory=$1 canonical uid directory_mode
|
||||
|
||||
failure_context="directory $directory"
|
||||
canonical=$(realpath -e -- "$directory")
|
||||
[[ $canonical == "$directory" && -d $directory && ! -L $directory ]]
|
||||
|
||||
while :; do
|
||||
failure_context="directory $directory (must be root-owned and not group- or world-writable)"
|
||||
uid=$(stat -c %u -- "$directory")
|
||||
directory_mode=$(stat -c %a -- "$directory")
|
||||
(( uid == 0 ))
|
||||
(( (8#$directory_mode & 0022) == 0 ))
|
||||
[[ $directory == "/" ]] && break
|
||||
directory=${directory%/*}
|
||||
[[ -n $directory ]] || directory=/
|
||||
done
|
||||
}
|
||||
|
||||
validate_trusted_configuration_file() {
|
||||
local configuration=$1 canonical uid configuration_mode size
|
||||
|
||||
failure_context="root configuration $configuration"
|
||||
[[ -f $configuration && ! -L $configuration ]]
|
||||
canonical=$(realpath -e -- "$configuration")
|
||||
[[ $canonical == "$configuration" ]]
|
||||
validate_trusted_directory "${configuration%/*}"
|
||||
uid=$(stat -c %u -- "$configuration")
|
||||
configuration_mode=$(stat -c %a -- "$configuration")
|
||||
size=$(stat -c %s -- "$configuration")
|
||||
(( uid == 0 ))
|
||||
(( (8#$configuration_mode & 0022) == 0 ))
|
||||
(( size > 0 && size <= 4096 ))
|
||||
}
|
||||
|
||||
# A packaged tree must be root-owned. A development checkout is the one
|
||||
# deliberate exception: omarchy dev link records its canonical path in a
|
||||
# root-owned /etc/omarchy.conf. That is already an explicit decision to run
|
||||
# privileged Omarchy commands from user-editable code in the checkout, so
|
||||
# reading its packaged assets does not widen the development trust boundary.
|
||||
development_source=false
|
||||
source_root_uid=$(stat -c %u -- "$source_root")
|
||||
if (( source_root_uid != 0 )); then
|
||||
printf "omarchy-plymouth-set: %s is not root-owned, so it cannot supply executable boot assets.\n" "$source_root" >&2
|
||||
printf "omarchy-plymouth-set: this is expected on a development checkout; run omarchy dev unlink to publish from /usr/share/omarchy.\n" >&2
|
||||
exit 1
|
||||
omarchy_conf=/etc/omarchy.conf
|
||||
failure_context="$source_root is user-owned and $omarchy_conf must contain its trusted dev-link authorization; run omarchy dev link to authorize it"
|
||||
failure_hint="$source_root is user-owned; run omarchy dev link to authorize this development checkout, or omarchy dev unlink to use the packaged tree"
|
||||
validate_trusted_configuration_file "$omarchy_conf"
|
||||
|
||||
quoted_source_root=$source_root
|
||||
quoted_source_root=${quoted_source_root//\\/\\\\}
|
||||
quoted_source_root=${quoted_source_root//\"/\\\"}
|
||||
quoted_source_root=${quoted_source_root//\$/\\\$}
|
||||
quoted_source_root=${quoted_source_root//\`/\\\`}
|
||||
expected_config_line="export OMARCHY_PATH=\"$quoted_source_root\""
|
||||
mapfile -t omarchy_config_lines <"$omarchy_conf"
|
||||
(( ${#omarchy_config_lines[@]} == 1 ))
|
||||
[[ ${omarchy_config_lines[0]} == "$expected_config_line" ]]
|
||||
development_source=true
|
||||
failure_hint=
|
||||
fi
|
||||
|
||||
if [[ $mode == "set" ]]; then
|
||||
@@ -126,25 +180,6 @@ run_root_transaction() {
|
||||
plymouth_default_assets=("${plymouth_theme_assets[@]}" logos/oma.png)
|
||||
sddm_theme_assets=(Main.qml bullet.png entry-failed.png entry.png lock-failed.png lock.png logo.png)
|
||||
|
||||
validate_trusted_directory() {
|
||||
local directory=$1 canonical uid directory_mode
|
||||
|
||||
failure_context="directory $directory"
|
||||
canonical=$(realpath -e -- "$directory")
|
||||
[[ $canonical == "$directory" && -d $directory && ! -L $directory ]]
|
||||
|
||||
while :; do
|
||||
failure_context="directory $directory (must be root-owned and not group- or world-writable)"
|
||||
uid=$(stat -c %u -- "$directory")
|
||||
directory_mode=$(stat -c %a -- "$directory")
|
||||
(( uid == 0 ))
|
||||
(( (8#$directory_mode & 0022) == 0 ))
|
||||
[[ $directory == "/" ]] && break
|
||||
directory=${directory%/*}
|
||||
[[ -n $directory ]] || directory=/
|
||||
done
|
||||
}
|
||||
|
||||
validate_trusted_file() {
|
||||
local source=$1 canonical uid file_mode size
|
||||
|
||||
@@ -152,13 +187,16 @@ run_root_transaction() {
|
||||
[[ -f $source && ! -L $source ]]
|
||||
canonical=$(realpath -e -- "$source")
|
||||
[[ $canonical == "$source" ]]
|
||||
validate_trusted_directory "${source%/*}"
|
||||
uid=$(stat -c %u -- "$source")
|
||||
file_mode=$(stat -c %a -- "$source")
|
||||
size=$(stat -c %s -- "$source")
|
||||
(( uid == 0 ))
|
||||
(( (8#$file_mode & 0022) == 0 ))
|
||||
(( size > 0 && size <= max_asset_size ))
|
||||
|
||||
if ! $development_source; then
|
||||
validate_trusted_directory "${source%/*}"
|
||||
uid=$(stat -c %u -- "$source")
|
||||
(( uid == 0 ))
|
||||
(( (8#$file_mode & 0022) == 0 ))
|
||||
fi
|
||||
}
|
||||
|
||||
copy_trusted_file() {
|
||||
|
||||
@@ -174,12 +174,14 @@ case "$1" in
|
||||
# simulation only, substitute trusted tools and map fixed system destinations
|
||||
# under the disposable fake root.
|
||||
code=${code/PATH=\/usr\/bin:\/bin/PATH=$TEST_ROOT_TOOLS:\/usr\/bin:\/bin}
|
||||
code=${code/omarchy_conf=\/etc\/omarchy.conf/omarchy_conf=$TEST_OMARCHY_CONF}
|
||||
code=${code/theme_dir=\/usr\/share\/plymouth\/themes\/omarchy/theme_dir=$TEST_FAKE_ROOT\/usr\/share\/plymouth\/themes\/omarchy}
|
||||
code=${code/sddm_dir=\/usr\/share\/sddm\/themes\/omarchy/sddm_dir=$TEST_FAKE_ROOT\/usr\/share\/sddm\/themes\/omarchy}
|
||||
|
||||
# Each rewrite above silently no-ops if the production text drifts, which
|
||||
# would point this simulation at the real /usr/share. Refuse instead.
|
||||
[[ $code == *"PATH=$TEST_ROOT_TOOLS:/usr/bin:/bin"* ]] || exit 94
|
||||
[[ $code == *"omarchy_conf=$TEST_OMARCHY_CONF"* ]] || exit 94
|
||||
[[ $code == *"theme_dir=$TEST_FAKE_ROOT/usr/share/plymouth/themes/omarchy"* ]] || exit 94
|
||||
[[ $code == *"sddm_dir=$TEST_FAKE_ROOT/usr/share/sddm/themes/omarchy"* ]] || exit 94
|
||||
|
||||
@@ -243,6 +245,7 @@ setup_run() {
|
||||
fake_root="$run_dir/root"
|
||||
sudo_log="$run_dir/sudo.log"
|
||||
leak_log="$run_dir/leaked-stage-path.log"
|
||||
omarchy_conf="$run_dir/omarchy.conf"
|
||||
theme="$fake_root/usr/share/plymouth/themes/omarchy"
|
||||
sddm="$fake_root/usr/share/sddm/themes/omarchy"
|
||||
|
||||
@@ -295,6 +298,7 @@ run_set_colors() {
|
||||
TEST_FAKE_ROOT="$fake_root" \
|
||||
TEST_STAGES="$stages" \
|
||||
TEST_ROOT_TOOLS="$root_tools" \
|
||||
TEST_OMARCHY_CONF="$omarchy_conf" \
|
||||
TEST_SUDO_LOG="$sudo_log" \
|
||||
TEST_LEAK_LOG="$leak_log" \
|
||||
"$@" \
|
||||
@@ -442,21 +446,49 @@ assert_no_temporary_files "$fake_root"
|
||||
|
||||
pass "root rejects packaged assets that a desktop process could rewrite"
|
||||
|
||||
# omarchy dev link points OMARCHY_PATH at a checkout the desktop user owns, so
|
||||
# this refusal fires on a working machine, not only under attack. Every check in
|
||||
# the privileged transaction is a bare assertion that aborts under set -e, so
|
||||
# without a diagnostic the whole Plymouth menu would just close in silence.
|
||||
# A random user-owned OMARCHY_PATH remains untrusted. Only the exact canonical
|
||||
# checkout recorded by root in /etc/omarchy.conf is the supported dev-link
|
||||
# exception; an unrelated or stale authorization must not weaken the check.
|
||||
setup_run
|
||||
output=$(run_set 022 env TEST_UNTRUSTED_SOURCE="$ROOT" 2>&1)
|
||||
status=$?
|
||||
|
||||
(( status != 0 )) || fail "a user-owned OMARCHY_PATH is rejected"
|
||||
[[ $output == *"is not root-owned"* ]] || fail "the refusal names the untrusted source tree" "$output"
|
||||
[[ $output == *"omarchy dev unlink"* ]] || fail "the refusal names the way back to a trusted tree" "$output"
|
||||
[[ $output == *"user-owned"* ]] || fail "the refusal names the untrusted source tree" "$output"
|
||||
[[ $output == *"omarchy dev link"* ]] || fail "the refusal names how to authorize a development checkout" "$output"
|
||||
[[ $(cat "$theme/bullet.png") == 'old plymouth bullet.png' ]] || fail "a user-owned OMARCHY_PATH leaves the live theme unchanged"
|
||||
assert_no_temporary_files "$fake_root"
|
||||
|
||||
pass "a development checkout is refused with an explanation instead of in silence"
|
||||
setup_run
|
||||
printf 'export OMARCHY_PATH="/some/other/checkout"\n' >"$omarchy_conf"
|
||||
chmod 0644 "$omarchy_conf"
|
||||
output=$(run_set 022 env TEST_UNTRUSTED_SOURCE="$ROOT" 2>&1)
|
||||
status=$?
|
||||
|
||||
(( status != 0 )) || fail "a stale dev-link authorization is rejected"
|
||||
[[ $(cat "$theme/bullet.png") == 'old plymouth bullet.png' ]] || fail "a stale dev-link authorization leaves the live theme unchanged"
|
||||
assert_no_temporary_files "$fake_root"
|
||||
|
||||
setup_run
|
||||
printf 'export OMARCHY_PATH="%s"\n' "$ROOT" >"$omarchy_conf"
|
||||
chmod 0666 "$omarchy_conf"
|
||||
output=$(run_set 022 env TEST_UNTRUSTED_SOURCE="$ROOT" 2>&1)
|
||||
status=$?
|
||||
|
||||
(( status != 0 )) || fail "a writable dev-link authorization is rejected"
|
||||
[[ $(cat "$theme/bullet.png") == 'old plymouth bullet.png' ]] || fail "a writable dev-link authorization leaves the live theme unchanged"
|
||||
assert_no_temporary_files "$fake_root"
|
||||
|
||||
setup_run
|
||||
printf 'export OMARCHY_PATH="%s"\n' "$ROOT" >"$omarchy_conf"
|
||||
chmod 0644 "$omarchy_conf"
|
||||
output=$(run_set 022 env TEST_UNTRUSTED_SOURCE="$ROOT" 2>&1)
|
||||
status=$?
|
||||
|
||||
(( status == 0 )) || fail "the root-authorized development checkout can publish Plymouth assets" "$output"
|
||||
cmp -s "$ROOT/default/plymouth/bullet.png" "$theme/bullet.png" || fail "the authorized development checkout supplies the packaged assets"
|
||||
|
||||
pass "only the checkout explicitly authorized by omarchy dev link may be user-owned"
|
||||
|
||||
# Root rejects both a symlinked parent and a group/world-writable parent before
|
||||
# it creates a temporary file or touches the live destination.
|
||||
@@ -509,6 +541,7 @@ output=$(
|
||||
TEST_FAKE_ROOT="$fake_root" \
|
||||
TEST_STAGES="$stages" \
|
||||
TEST_ROOT_TOOLS="$root_tools" \
|
||||
TEST_OMARCHY_CONF="$omarchy_conf" \
|
||||
TEST_SUDO_LOG="$sudo_log" \
|
||||
TEST_LEAK_LOG="$leak_log" \
|
||||
/bin/bash "$ROOT/bin/omarchy-refresh-plymouth" 2>&1
|
||||
|
||||
Reference in New Issue
Block a user