Files
omarchy/migrations/1788662350.sh
T
acrogenesis feb0557e1d Harden ownership of installed sleep hooks
Publish privileged sleep-hook and hybrid-GPU files through root-owned replacement inodes, repair unsafe existing copies while preserving administrator customizations, and keep partial hibernation setup retryable.

Reported-by: Roger Piñol <rogerpicar@gmail.com>
2026-09-06 14:36:27 -06:00

304 lines
9.6 KiB
Bash

echo "Repair user-owned system-sleep hooks and hybrid GPU service configuration"
system_sleep_dir=/usr/lib/systemd/system-sleep
supergfxd_drop_in=/etc/systemd/system/supergfxd.service.d/delay-start.conf
quarantine_root=/var/lib/omarchy/migrations/1788662350-system-sleep
reload_needed_marker=/var/lib/omarchy/migrations/1788662350-systemd-reload-needed
keyboard_source="$OMARCHY_PATH/default/systemd/system-sleep/keyboard-backlight"
force_igpu_source="$OMARCHY_PATH/default/systemd/system-sleep/force-igpu"
supergfxd_source="$OMARCHY_PATH/default/systemd/system/supergfxd.service.d/delay-start.conf"
legacy_keyboard_sha256=f313a81e47401f0d38b8602e5997f52c5286d5e97f74027564ddd515b3d16511
legacy_force_igpu_sha256=d604e7c4903829563e45fc52188fc5602c3f1bc66e247f0a2cc0a974ed6e57db
as_root() {
if (( EUID == 0 )); then
"$@"
else
sudo "$@"
fi
}
path_is_root_controlled() {
local path="$1"
local current=/ component candidate file_mode link metadata part status uid gid mode
local missing_depth=0 symlink_count=0
local -a pending resolved link_components
[[ $path == /* ]] || return 1
IFS=/ read -r -a pending <<<"$path"
# A non-root group is harmless when neither it nor everyone else can write.
# Resolve symlinks component by component so an indirect link cannot hide an
# intermediate directory controlled by an unprivileged user.
metadata=$(path_metadata /) || return 1
read -r file_mode uid gid mode <<<"$metadata"
(( uid == 0 && (8#$mode & 8#022) == 0 )) || return 1
while ((${#pending[@]})); do
component=${pending[0]}
pending=("${pending[@]:1}")
[[ -n $component ]] || continue
[[ $component == "." ]] && continue
if [[ $component == ".." ]]; then
if ((${#resolved[@]})); then
unset 'resolved[-1]'
fi
current=/
for part in "${resolved[@]}"; do
if [[ $current == "/" ]]; then
current="/$part"
else
current="$current/$part"
fi
done
if (( missing_depth > 0 && ${#resolved[@]} < missing_depth )); then
missing_depth=0
fi
continue
fi
if [[ $current == "/" ]]; then
candidate="/$component"
else
candidate="$current/$component"
fi
if (( missing_depth > 0 )); then
# The first missing component makes descendants inactive today, but keep
# consuming the lexical suffix. A later .. can escape back into an
# existing user-controlled path that would become active if an
# administrator creates the missing directory.
resolved+=("$component")
current=$candidate
continue
elif metadata=$(path_metadata "$candidate"); then
read -r file_mode uid gid mode <<<"$metadata"
else
status=$?
if (( status == 2 )); then
resolved+=("$component")
current=$candidate
missing_depth=${#resolved[@]}
continue
else
return 1
fi
fi
if (( (16#$file_mode & 16#f000) == 16#a000 )); then
((++symlink_count <= 40)) || return 1
link=$(readlink_with_privilege "$candidate") || return 1
IFS=/ read -r -a link_components <<<"$link"
pending=("${link_components[@]}" "${pending[@]}")
if [[ $link == /* ]]; then
resolved=()
current=/
fi
continue
fi
(( uid == 0 && (8#$mode & 8#022) == 0 )) || return 1
resolved+=("$component")
current=$candidate
done
}
path_metadata() {
local path="$1"
local metadata parent
if /usr/bin/stat -c '%f %u %g %a' -- "$path" 2>/dev/null; then
return 0
elif [[ ! -e $path && ! -L $path ]]; then
parent=${path%/*}
[[ -n $parent ]] || parent=/
# Avoid asking for sudo for ordinary ENOENT. If the parent is searchable,
# the absence is conclusive; an inaccessible root-only chain still needs a
# privileged metadata check so safe administrator symlinks are preserved.
[[ -x $parent ]] && return 2
if metadata=$(as_root /usr/bin/stat -c '%f %u %g %a' -- "$path" 2>/dev/null); then
printf '%s\n' "$metadata"
return 0
elif as_root /usr/bin/test -x "$parent"; then
# The privileged probe could search the protected parent, so stat's
# failure identifies a target that does not exist yet.
return 2
else
return 1
fi
else
as_root /usr/bin/stat -c '%f %u %g %a' -- "$path"
fi
}
readlink_with_privilege() {
local path="$1"
if /usr/bin/readlink -- "$path" 2>/dev/null; then
return 0
else
as_root /usr/bin/readlink -- "$path"
fi
}
privileged_entry_is_safe() {
local path="$1"
path_is_root_controlled "$path"
}
file_matches_source() {
local source="$1"
local destination="$2"
[[ -f $destination && ! -L $destination ]] || return 1
if [[ -r $destination ]]; then
/usr/bin/cmp -s -- "$source" "$destination"
else
as_root /usr/bin/cmp -s -- "$source" "$destination"
fi
}
file_matches_sha256() {
local destination="$1"
local expected="$2"
local digest
[[ -f $destination && ! -L $destination ]] || return 1
if [[ -r $destination ]]; then
digest=$(/usr/bin/sha256sum -- "$destination") || return 1
else
digest=$(as_root /usr/bin/sha256sum -- "$destination") || return 1
fi
[[ ${digest%% *} == "$expected" ]]
}
safe_stage_path() {
local stage="$1"
local destination="$2"
local prefix suffix
prefix="${destination%/*}/.${destination##*/}.omarchy."
[[ $stage == "$prefix"* ]] || return 1
suffix=${stage#"$prefix"}
[[ $suffix =~ ^[[:alnum:]]{6}$ ]]
}
install_root_file() {
local source="$1"
local destination="$2"
local mode="$3"
local stage
stage=$(as_root /usr/bin/mktemp -- "${destination%/*}/.${destination##*/}.omarchy.XXXXXX") || return 1
safe_stage_path "$stage" "$destination" || return 1
if as_root /usr/bin/install -m "$mode" -o root -g root -T "$source" "$stage" &&
as_root /usr/bin/mv -Tf -- "$stage" "$destination"; then
return 0
else
safe_stage_path "$stage" "$destination" && as_root /usr/bin/rm -f -- "$stage"
return 1
fi
}
preserve_unsafe_customization() {
local path="$1"
local label="$2"
local backup_dir backup
if ! as_root /usr/bin/install -d -m 0700 -o root -g root "$quarantine_root"; then
echo "Could not create the root-only system-sleep quarantine at $quarantine_root" >&2
return 1
fi
if ! backup_dir=$(as_root /usr/bin/mktemp -d -- "$quarantine_root/${label}.XXXXXX"); then
echo "Could not reserve a quarantine path for $path" >&2
return 1
fi
backup="$backup_dir/original"
if as_root /usr/bin/cp -a --no-dereference -T -- "$path" "$backup"; then
printf '%s\n' "$backup"
else
as_root /usr/bin/rm -rf -- "$backup_dir"
echo "Could not preserve unsafe custom content from $path before repairing it" >&2
return 1
fi
}
repair_unsafe_privileged_entry() {
local source="$1"
local destination="$2"
local mode="$3"
local label="$4"
local legacy_sha256="${5:-}"
local backup current_mode
[[ -e $destination || -L $destination ]] || return 0
[[ -f $destination || -L $destination ]] || return 0
if file_matches_source "$source" "$destination"; then
current_mode=$(/usr/bin/stat -c '%a' -- "$destination" 2>/dev/null) ||
current_mode=$(as_root /usr/bin/stat -c '%a' -- "$destination") || return 1
if privileged_entry_is_safe "$destination" && [[ $current_mode == "${mode#0}" ]]; then
return 0
fi
elif [[ -n $legacy_sha256 ]] && file_matches_sha256 "$destination" "$legacy_sha256"; then
:
else
privileged_entry_is_safe "$destination" && return 0
backup=$(preserve_unsafe_customization "$destination" "$label") || return 1
fi
if install_root_file "$source" "$destination" "$mode"; then
if [[ -n ${backup:-} ]]; then
echo "Preserved unsafe custom content from $destination at $backup for administrator review" >&2
fi
else
if [[ -n ${backup:-} ]]; then
echo "Preserved unsafe custom content from $destination at $backup, but could not repair the active path" >&2
fi
return 1
fi
}
# Replace rather than chown an unsafe destination: its current owner may have
# already changed the contents or kept a writable file descriptor open. The
# root-owned staging inode makes the final rename an atomic trust transition.
repair_unsafe_privileged_entry "$keyboard_source" \
"$system_sleep_dir/keyboard-backlight" 0755 keyboard-backlight "$legacy_keyboard_sha256"
force_igpu="$system_sleep_dir/force-igpu"
repair_unsafe_privileged_entry "$force_igpu_source" "$force_igpu" 0755 force-igpu "$legacy_force_igpu_sha256"
systemd_reload_needed=false
if [[ -e $reload_needed_marker || -L $reload_needed_marker ]]; then
systemd_reload_needed=true
fi
if [[ -e $supergfxd_drop_in || -L $supergfxd_drop_in ]]; then
if ! privileged_entry_is_safe "$supergfxd_drop_in"; then
# Replacing the drop-in and reloading systemd are one repair. Record the
# second half before changing the file so failure or interruption cannot
# be forgotten when a retry sees only the trusted replacement on disk.
if ! as_root /usr/bin/install -Dm0644 -o root -g root /dev/null "$reload_needed_marker"; then
echo "Could not persist the pending systemd reload for the repaired supergfxd configuration" >&2
exit 1
fi
systemd_reload_needed=true
repair_unsafe_privileged_entry "$supergfxd_source" "$supergfxd_drop_in" 0644 delay-start.conf
fi
fi
if $systemd_reload_needed; then
if ! as_root /usr/bin/systemctl daemon-reload; then
echo "Could not reload systemd after repairing the supergfxd configuration; the migration will retry" >&2
exit 1
fi
if ! as_root /usr/bin/rm -f -- "$reload_needed_marker"; then
echo "Could not clear the pending systemd reload marker; the migration will retry" >&2
exit 1
fi
fi