Model what each parser does with an empty and a dangling directive

Review of the previous commits turned up four places where the predicates
and their tests disagreed with the tools they are modelling, each checked
against udevadm verify, systemd-analyze verify and visudo -cf rather than
against reading of the sources.

An empty ExecStop= resets the list, so a unit an administrator neutralised
that way runs nothing at shutdown and is no longer ours to remove; the
predicate now tracks the last state instead of returning on the first home
path it sees. A file whose last line ends in a backslash still carries a
live directive for systemd, so the pending logical line is emitted at EOF;
udev ignores such a line and sudo rejects the file outright, so this costs
those two nothing. The scanner's taint pass now reads += appends, which its
own comment already promised: the value of an append is no use, but a name
that reaches a user root through one has to be judged on it.

Two regression guards passed against the implementations they were written
for. The udev continuation fixture put the whole RUN+= below the comment, so
it matched whether or not the pending half was carried across; the split now
falls inside the RUN+= value. The sudoers one kept its file on the strength
of a spec above the comment, so it could not fail either; the hand-written
spec now sits below. Both fail against a mutant that discards the pending
line. The comment above the second also claimed a continued comment stays a
comment, which visudo contradicts.
This commit is contained in:
acrogenesis
2026-08-29 19:23:02 -06:00
parent d593847728
commit 394c1371c9
7 changed files with 90 additions and 19 deletions
+16 -2
View File
@@ -67,6 +67,12 @@ active_lines() {
emit_logical "$logical$line" emit_logical "$logical$line"
logical="" logical=""
done done
# A file whose last line ends in a backslash still carries a live directive for
# systemd: `systemd-analyze verify` resolves an ExecStop= written that way.
# udev ignores the dangling line and sudo rejects the file outright, so emitting
# it costs those two nothing.
emit_logical "$logical"
} }
# One logical line, whitespace collapsed so a reformatted copy still compares # One logical line, whitespace collapsed so a reformatted copy still compares
@@ -195,6 +201,7 @@ plymouth_unit_runs_from_home() {
local home_pattern="^(/home/[^/]+|/root)/\\.local/share/omarchy/bin/$binary\$" local home_pattern="^(/home/[^/]+|/root)/\\.local/share/omarchy/bin/$binary\$"
local line word local line word
local -a words local -a words
local matched=1
while IFS= read -r line; do while IFS= read -r line; do
if [[ ! $line =~ $exec_stop_pattern ]]; then if [[ ! $line =~ $exec_stop_pattern ]]; then
@@ -203,6 +210,11 @@ plymouth_unit_runs_from_home() {
read -ra words <<<"${BASH_REMATCH[1]}" read -ra words <<<"${BASH_REMATCH[1]}"
if (( ! ${#words[@]} )); then if (( ! ${#words[@]} )); then
# An empty assignment resets the list, so nothing named before it still
# runs. `systemd-analyze verify` reports the missing command for a unit
# with one ExecStop=, and reports nothing once a bare ExecStop= follows it.
# An administrator who neutralised the unit this way is left alone.
matched=1
continue continue
fi fi
@@ -214,11 +226,13 @@ plymouth_unit_runs_from_home() {
done done
if [[ $word =~ $home_pattern || $word == "$HOME/.local/share/omarchy/bin/$binary" ]]; then if [[ $word =~ $home_pattern || $word == "$HOME/.local/share/omarchy/bin/$binary" ]]; then
return 0 matched=0
else
matched=1
fi fi
done < <(active_lines systemd) done < <(active_lines systemd)
return 1 return $matched
} }
# /etc/sudoers.d is 0750 root:root as shipped, and omarchy-migrate runs as the # /etc/sudoers.d is 0750 root:root as shipped, and omarchy-migrate runs as the
View File
View File
@@ -213,14 +213,16 @@ run_migration
fail "migration matches the binary the filename promises, not any home path" fail "migration matches the binary the filename promises, not any home path"
pass "migration matches the binary the filename promises, not any home path" pass "migration matches the binary the filename promises, not any home path"
# udev resumes a continuation across a comment: `udevadm verify` on # udev resumes a continuation across a comment: `udevadm verify` reports its
# 'SUBSYSTEM=="power_supply" \' + "# c" + ', RUN+="..."' reports its style warning # complaint on line 1 for a rule split this way, so the three lines are one live
# on line 1, so those three lines are one rule and the rule is live. # rule. The split falls inside the RUN+= value on purpose -- with the whole
# RUN+= below the comment the assertion passes even against an implementation
# that throws the pending half away, which is the shape this guards against.
reset_machine reset_machine
cat >"$power_rule" <<'RULE' cat >"$power_rule" <<'RULE'
SUBSYSTEM=="power_supply", ATTR{type}=="Mains" \ SUBSYSTEM=="power_supply", ATTR{type}=="Mains", RUN+="/usr/bin/systemd-run --no-block --unit=omarchy-power-profile \
# split for readability # split for readability
, RUN+="/home/someuser/.local/share/omarchy/bin/omarchy-powerprofiles-set" /home/someuser/.local/share/omarchy/bin/omarchy-powerprofiles-set"
RULE RULE
run_migration run_migration
+11 -6
View File
@@ -176,16 +176,17 @@ declare -A VARS_TAINTED=()
# isolation would miss in whichever direction it picked. # isolation would miss in whichever direction it picked.
collect_vars() { collect_vars() {
local -n source_lines="$1" local -n source_lines="$1"
local line name value local line name value append
VARS=() VARS=()
VARS_TAINTED=() VARS_TAINTED=()
for line in "${source_lines[@]}"; do for line in "${source_lines[@]}"; do
[[ $line =~ ^[[:space:]]*# ]] && continue [[ $line =~ ^[[:space:]]*# ]] && continue
[[ $line =~ ^[[:space:]]*(local|declare|export|readonly|typeset)?[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]] || continue [[ $line =~ ^[[:space:]]*(local|declare|export|readonly|typeset)?[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)(\+?)=(.*)$ ]] || continue
name=${BASH_REMATCH[2]} name=${BASH_REMATCH[2]}
value=${BASH_REMATCH[3]} append=${BASH_REMATCH[3]}
value=${BASH_REMATCH[4]}
value=${value%%[[:space:]]#*} value=${value%%[[:space:]]#*}
value=${value%[[:space:]]} value=${value%[[:space:]]}
if [[ $value == \"*\" || $value == \'*\' ]]; then if [[ $value == \"*\" || $value == \'*\' ]]; then
@@ -193,6 +194,11 @@ collect_vars() {
fi fi
mentions_user_writable_root "$value" && VARS_TAINTED["$name"]=1 mentions_user_writable_root "$value" && VARS_TAINTED["$name"]=1
# An append never wins the value -- an array grown across a file resolves to
# nothing useful -- but it does carry the taint, or a name could reach a user
# root through += and never be judged on it.
[[ -n $append ]] && continue
[[ -v VARS[$name] ]] || VARS["$name"]=$value [[ -v VARS[$name] ]] || VARS["$name"]=$value
done done
} }
@@ -457,9 +463,8 @@ privileged_destination() {
fi fi
done < <(command_destinations "$line") done < <(command_destinations "$line")
# An elevated write whose destination cannot be resolved counts as # An elevated write whose destination cannot be resolved counts as privileged:
# privileged: sudo tee is not aimed at a user's own dotfile, and assuming # sudo tee is not aimed at a user's own dotfile.
# otherwise is how this bug class survived six reviews.
if ((elevated == 0)) && ((${#unresolved[@]} > 0)); then if ((elevated == 0)) && ((${#unresolved[@]} > 0)); then
printf '%s' "${unresolved[0]} (unresolved destination of an elevated write)" printf '%s' "${unresolved[0]} (unresolved destination of an elevated write)"
return 0 return 0
@@ -197,21 +197,25 @@ run_migration
fail "migration keeps a same-named file that never cleaned itself up" fail "migration keeps a same-named file that never cleaned itself up"
pass "migration keeps a same-named file that never cleaned itself up" pass "migration keeps a same-named file that never cleaned itself up"
# A rule continued onto the next line is one logical line, and a comment that is # A spec continued onto the next line is one logical line, and a comment's own
# continued stays a comment for the whole of it. # trailing backslash swallows nothing: `visudo -cf` on "# note \" plus a bogus
# token reports the error on line 2, so the spec below a commented line is live.
# The hand-written spec sits under the comment on purpose -- above it, the file
# is kept under either reading and the assertion cannot fail.
reset_machine reset_machine
cat >"$first_run" <<'EOF' cat >"$first_run" <<'EOF'
# Retired, keeping the old grant here for reference: \ # Retired, keeping the old grant here for reference: \
installer ALL=(ALL) NOPASSWD: /usr/bin/systemctl installer ALL=(ALL) NOPASSWD: /usr/local/bin/our-own-deploy-script
Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run Cmnd_Alias FIRST_RUN_CLEANUP = /bin/rm -f /etc/sudoers.d/first-run
installer ALL=(ALL) NOPASSWD: \ installer ALL=(ALL) NOPASSWD: \
/usr/local/bin/our-own-deploy-script /usr/bin/systemctl
installer ALL=(ALL) NOPASSWD: FIRST_RUN_CLEANUP
EOF EOF
run_migration run_migration
[[ -e $first_run ]] || [[ -e $first_run ]] ||
fail "migration reads a continued line as one rule and a continued comment as comment" fail "migration keeps a spec left live under a commented continuation"
pass "migration reads a continued line as one rule and a continued comment as comment" pass "migration keeps a spec left live under a commented continuation"
reset_machine reset_machine
printf 'installer ALL=(ALL) NOPASSWD: %s/.local/bin/tsui\n' "$home_dir" >"$tsui" printf 'installer ALL=(ALL) NOPASSWD: %s/.local/bin/tsui\n' "$home_dir" >"$tsui"
@@ -454,3 +458,49 @@ run_migration
[[ ! -e $plymouth_unit ]] || [[ ! -e $plymouth_unit ]] ||
fail "migration removes a unit whose ExecStop continues across a comment" "$(cat "$plymouth_unit")" fail "migration removes a unit whose ExecStop continues across a comment" "$(cat "$plymouth_unit")"
pass "migration removes a unit whose ExecStop continues across a comment" pass "migration removes a unit whose ExecStop continues across a comment"
# An empty ExecStop= resets the list: `systemd-analyze verify` reports the missing
# command for a unit with one ExecStop=, and reports nothing once a bare
# ExecStop= follows it. An administrator who neutralised the unit that way runs
# nothing at shutdown and keeps their file.
reset_machine
cat >"$plymouth_unit" <<'EOF'
[Service]
Type=oneshot
ExecStart=/usr/bin/true
ExecStop=/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync
ExecStop=
EOF
before=$(cat "$plymouth_unit")
run_migration
[[ -e $plymouth_unit ]] ||
fail "migration keeps a unit whose ExecStop list was reset to empty"
[[ $(cat "$plymouth_unit") == "$before" ]] ||
fail "migration leaves that unit byte for byte"
pass "migration keeps a unit whose ExecStop list was reset to empty"
# A reset followed by a fresh home ExecStop= is live again.
reset_machine
cat >"$plymouth_unit" <<'EOF'
[Service]
Type=oneshot
ExecStart=/usr/bin/true
ExecStop=
ExecStop=/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync
EOF
run_migration
[[ ! -e $plymouth_unit ]] ||
fail "migration removes a unit whose ExecStop is set again after a reset"
pass "migration removes a unit whose ExecStop is set again after a reset"
# systemd honours a directive whose line ends the file mid-continuation:
# `systemd-analyze verify` resolves an ExecStop= written that way.
reset_machine
printf '[Service]\nType=oneshot\nExecStart=/usr/bin/true\nExecStop=/home/installer/.local/share/omarchy/bin/omarchy-plymouth-shutdown-sync \\\n' >"$plymouth_unit"
run_migration
[[ ! -e $plymouth_unit ]] ||
fail "migration removes a unit whose last line ends mid-continuation"
pass "migration removes a unit whose last line ends mid-continuation"