Fail the suite on privileged writes through unquoted heredocs
An installer that writes a root-owned file through a heredoc with an unquoted delimiter (<<EOF rather than <<'EOF') has the installing user's shell expand the body first, so a user-controlled value is baked in as a literal. Send that into /etc and root later reads or executes a path the unprivileged user picked: a udev rule carrying $HOME/.local/share/omarchy/bin/... resolves through a symlink that user owns, so replacing the symlink gets their code run as root. Add the static check. A heredoc is flagged when its delimiter is unquoted, its body contains an install-time expansion (escaped \$VAR does not count, since that is left for a root daemon to expand at runtime), and its output reaches /etc, /usr, /opt, /srv, /boot or /var/lib via sudo tee, sudo dd, a redirect, or an install/cp/mv of the generated scratch file. Destinations written as variables are resolved from the file's own assignments. Sites that genuinely need install-time expansion declare it inline: # omarchy:heredoc-expands paths=none -- $servers is a validated IP list paths= is machine-checked against the expansions the scanner finds to be path-shaped, so this cannot become a rubber stamp: adding a $HOME/... to an already-annotated heredoc makes the declaration false and trips the check again. Path expansions anchored under a root-owned prefix, as in "/etc/systemd/system/$unit", are correctly not path-shaped. Annotate the sites the scan reports, each of which expands a scalar: DNS addresses in omarchy-dns, a literal PAM line in omarchy-setup-security-fingerprint, kernel cmdline parameters and usernames in omarchy-upgrade-to-quattro. omarchy-provision-owner expanded a unit name that was already a constant, so its delimiter is now quoted and the name hardcoded; the generated unit file is byte-identical. omarchy-windows-vm declares paths=storage,shared, the only site that interpolates a user-chosen path. Fixtures prove non-vacuity in both directions: the write routes other than a pipe into sudo tee, the shapes that must stay quiet, udev rules and a shutdown unit taken verbatim from this repository's history, and the rubber-stamp case where a paths=none annotation on a baked $HOME path still fails.
This commit is contained in:
@@ -149,6 +149,9 @@ write_networkmanager_dns() {
|
||||
local servers="$1"
|
||||
|
||||
install -d -m 0755 "$(dirname "$NM_DNS_CONF")"
|
||||
# omarchy:heredoc-expands paths=none -- $servers is a space-separated list of
|
||||
# validated DNS server addresses, not a path; nothing user-writable is baked
|
||||
# into the root-owned drop-in.
|
||||
cat >"$NM_DNS_CONF" <<EOF
|
||||
# Managed by omarchy-dns. Remove this file or run omarchy dns DHCP to use DHCP DNS again.
|
||||
[global-dns]
|
||||
@@ -302,6 +305,9 @@ Custom)
|
||||
split_dns_servers "$dns_servers"
|
||||
write_networkmanager_dns "$dns_servers"
|
||||
set_connection_dns "$ipv4_dns" "$ipv6_dns"
|
||||
# omarchy:heredoc-expands paths=none -- $dns_servers holds DNS addresses that
|
||||
# normalize_servers has already validated; the //,/ turns the comma list into
|
||||
# the space list resolved.conf wants. No path is interpolated.
|
||||
tee /etc/systemd/resolved.conf >/dev/null <<EOF
|
||||
[Resolve]
|
||||
DNS=${dns_servers//,/ }
|
||||
|
||||
@@ -788,7 +788,7 @@ configure_login() {
|
||||
# After=) is what makes it deterministic — no sleep/race against SDDM's startup.
|
||||
install_autologin_once_cleanup() {
|
||||
local unit=omarchy-provision-autologin-once.service
|
||||
cat >"/etc/systemd/system/$unit" <<UNIT
|
||||
cat >"/etc/systemd/system/$unit" <<'UNIT'
|
||||
[Unit]
|
||||
Description=Drop the first-boot autologin before the next login
|
||||
Before=display-manager.service
|
||||
@@ -797,7 +797,7 @@ ConditionPathExists=/etc/sddm.conf.d/autologin.conf
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/bin/rm -f /etc/sddm.conf.d/autologin.conf
|
||||
ExecStartPost=/usr/bin/rm -f /etc/systemd/system/graphical.target.wants/$unit /etc/systemd/system/$unit
|
||||
ExecStartPost=/usr/bin/rm -f /etc/systemd/system/graphical.target.wants/omarchy-provision-autologin-once.service /etc/systemd/system/omarchy-provision-autologin-once.service
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
|
||||
@@ -41,6 +41,9 @@ setup_pam_config() {
|
||||
fi
|
||||
else
|
||||
echo "Creating polkit configuration with fingerprint authentication..."
|
||||
# omarchy:heredoc-expands paths=none -- $fprintd_gate is the literal PAM
|
||||
# line defined above, shared with the two sed insertions so the gate cannot
|
||||
# drift between files. The only path in it is the fixed /usr/bin one.
|
||||
sudo tee /etc/pam.d/polkit-1 >/dev/null <<EOF
|
||||
$fprintd_gate
|
||||
auth sufficient pam_fprintd.so
|
||||
|
||||
@@ -597,6 +597,9 @@ preserve_kernel_cmdline_root() {
|
||||
fi
|
||||
|
||||
log "Preserving the kernel cmdline root parameters in $default_conf"
|
||||
# omarchy:heredoc-expands paths=none -- ${boot_params[*]} is the kernel
|
||||
# cmdline fragment assembled above from findmnt output (root=, rootflags=),
|
||||
# not a filesystem path this script controls.
|
||||
as_root tee -a "$default_conf" >/dev/null <<EOF
|
||||
# Written by omarchy-upgrade-to-quattro. The += drop-ins in
|
||||
# /etc/limine-entry-tool.d/ stop limine-entry-tool from reading
|
||||
@@ -1395,6 +1398,9 @@ EOF
|
||||
autologin_user=$(as_root awk -F= '/^User=/ { print $2; exit }' /etc/sddm.conf.d/autologin.conf 2>/dev/null || true)
|
||||
fi
|
||||
[[ -n ${autologin_user:-} ]] || autologin_user="$target_user"
|
||||
# omarchy:heredoc-expands paths=none -- $autologin_user is a username, read
|
||||
# back from the root-owned drop-in or falling back to $target_user. Same
|
||||
# mechanism as the old getty override: a name expands, no path does.
|
||||
cat <<EOF | as_root tee /etc/sddm.conf.d/autologin.conf >/dev/null
|
||||
[Autologin]
|
||||
User=$autologin_user
|
||||
@@ -1405,6 +1411,8 @@ EOF
|
||||
fi
|
||||
|
||||
as_root install -d -m 0755 -o sddm -g sddm /var/lib/sddm 2>/dev/null || as_root install -d -m 0755 /var/lib/sddm
|
||||
# omarchy:heredoc-expands paths=none -- $target_user is a username, not a
|
||||
# path; SDDM's state file records who logged in last.
|
||||
cat <<EOF | as_root tee /var/lib/sddm/state.conf >/dev/null
|
||||
[Last]
|
||||
Session=omarchy.desktop
|
||||
|
||||
@@ -691,6 +691,10 @@ write_compose_atomically() (
|
||||
esc_password=${esc_password//\$/\$\$}
|
||||
|
||||
tmp=$(mktemp "$RUNTIME_DIR/.compose.XXXXXX") || exit 1
|
||||
# omarchy:heredoc-expands paths=EXPECTED_STORAGE,EXPECTED_SHARED -- both are
|
||||
# root-protected anchors derived from the authenticated caller uid and bound
|
||||
# to source inodes that were opened and validated before this compose is
|
||||
# written. The remaining expansions are revalidated scalar settings.
|
||||
cat >"$tmp" <<EOF || exit 1
|
||||
services:
|
||||
windows:
|
||||
|
||||
Reference in New Issue
Block a user