* Revert "Merge pull request #11874 from acrogenesis/feature/usb-device-authorization" This reverts commit5cb31317b4, reversing changes made to9d48fc402e. * Undo USB and Thunderbolt device approval on machines that ran it * Verify boot rebuilds, recover Thunderbolt journals and prove USBGuard ownership in the removal migration * Read the boot partition from ESP_PATH and stop on failed Bolt reads in the removal migration * Recover Bolt's original mode from the setup journal in the removal migration
213 lines
9.3 KiB
Bash
213 lines
9.3 KiB
Bash
echo "Remove approval for new USB and Thunderbolt devices"
|
|
|
|
# Approval for new accessories (#11874) shipped briefly to edge and dev. The
|
|
# package update has already removed its commands, units and Polkit rules; this
|
|
# undoes what they left on the machine so devices connect as they did before.
|
|
|
|
usb_state=$HOME/.local/state/omarchy/usb-authorization
|
|
tb_dir=/var/lib/omarchy/thunderbolt-authorization
|
|
usb_boot_drop_in=/etc/limine-entry-tool.d/usb-authorization.conf
|
|
usb_boot_pending=/var/lib/omarchy/usb-authorization-boot.removing
|
|
usb_boot_parameter=usbcore.authorized_default=0
|
|
usbguard_shipped=1791573373 # when usbguard joined the default packages
|
|
# The access every Omarchy path granted with `usbguard add-user`.
|
|
usbguard_grant=$'Devices=list,modify,listen\nPolicy=list\nExceptions=listen'
|
|
|
|
# The approval watchers. Their unit files are gone, so systemctl disable can't
|
|
# find them; drop the enablement links directly.
|
|
for unit in omarchy-usb-authorization.service omarchy-thunderbolt-authorization.service; do
|
|
systemctl --user stop "$unit" 2>/dev/null || true
|
|
rm -f "$HOME"/.config/systemd/user/*.wants/"$unit"
|
|
done
|
|
rm -f "$HOME/.config/systemd/user/omarchy-usb-authorization.service"
|
|
systemctl --user daemon-reload
|
|
|
|
bolt_list() {
|
|
sudo busctl --system --json=short call org.freedesktop.bolt /org/freedesktop/bolt org.freedesktop.bolt1.Manager "$1" | jq -r '.data[0][]'
|
|
}
|
|
|
|
# A device or controller missing from Bolt's inventory has nothing to restore.
|
|
# Any other failed read stops the migration, so it is retried.
|
|
bolt_has() {
|
|
grep -qxF "/org/freedesktop/bolt/$1/${2//-/_}" <<<"$bolt_known"
|
|
}
|
|
|
|
bolt_get() {
|
|
sudo busctl --system --json=short get-property org.freedesktop.bolt "/org/freedesktop/bolt/$1/${2//-/_}" "org.freedesktop.bolt1.$3" "$4" | jq -c .data
|
|
}
|
|
|
|
if [[ -d $tb_dir || -e /etc/omarchy/thunderbolt-authorization.enabled || -e /etc/omarchy/thunderbolt-authorization.pending ]]; then
|
|
sudo systemctl stop omarchy-thunderbolt-authorization.service 2>/dev/null || true
|
|
sudo rm -f /etc/systemd/system/multi-user.target.wants/omarchy-thunderbolt-authorization.service \
|
|
/etc/omarchy/thunderbolt-authorization.enabled /etc/omarchy/thunderbolt-authorization.pending
|
|
sudo systemctl daemon-reload
|
|
|
|
state=$(sudo cat "$tb_dir/policy.json" 2>/dev/null || echo '{}')
|
|
recovery=$(sudo cat "$tb_dir/boot-recovery.json" 2>/dev/null || echo '{}')
|
|
|
|
# Thunderbolt Boot replaced device policies and emptied the firmware boot
|
|
# allowlists. The originals were saved when it was turned on, or only in the
|
|
# recovery journal if turning it on was interrupted.
|
|
firmware=$(jq -c --argjson recovery "$recovery" \
|
|
'.boot_original // (if $recovery.policies then $recovery | {policies, acls} else {} end)' <<<"$state")
|
|
|
|
bolt_devices=$(bolt_list ListDevices)
|
|
bolt_domains=$(bolt_list ListDomains)
|
|
bolt_known=$bolt_devices$'\n'$bolt_domains
|
|
|
|
while read -r uid policy; do
|
|
if bolt_has devices "$uid"; then
|
|
current=$(bolt_get devices "$uid" Device Policy)
|
|
if [[ $current != "\"$policy\"" ]]; then
|
|
sudo boltctl config device.policy "$uid" "$policy"
|
|
fi
|
|
fi
|
|
done < <(jq -r '.policies // {} | to_entries[] | "\(.key) \(.value)"' <<<"$firmware")
|
|
|
|
# Bolt rejects a BootACL write that changes nothing, so compare first.
|
|
unrestored=0
|
|
while read -r uid; do
|
|
acl=$(jq -c --arg uid "$uid" '.acls[$uid]' <<<"$firmware")
|
|
if ! bolt_has domains "$uid"; then
|
|
echo "Thunderbolt controller $uid is not present; its firmware boot allowlist was not restored."
|
|
unrestored=1
|
|
continue
|
|
fi
|
|
current=$(bolt_get domains "$uid" Domain BootACL)
|
|
if [[ $current != "$acl" ]]; then
|
|
mapfile -t entries < <(jq -r '.[]' <<<"$acl")
|
|
sudo busctl --system set-property org.freedesktop.bolt "/org/freedesktop/bolt/domains/${uid//-/_}" \
|
|
org.freedesktop.bolt1.Domain BootACL as "${#entries[@]}" "${entries[@]}"
|
|
fi
|
|
done < <(jq -r '.acls // {} | keys[]' <<<"$firmware")
|
|
|
|
# Accessories approved here were stored with Bolt's manual policy, which
|
|
# Bolt won't authorize on its own. Hand back the ones Omarchy stored, as the
|
|
# removal command did, never a manual policy somebody else set.
|
|
while read -r uid store_time; do
|
|
if bolt_has devices "$uid"; then
|
|
current=$(bolt_get devices "$uid" Device Policy)
|
|
stored=$(bolt_get devices "$uid" Device StoreTime)
|
|
if [[ $current == '"manual"' && $stored == "$store_time" ]]; then
|
|
sudo boltctl config device.policy "$uid" auto
|
|
fi
|
|
fi
|
|
done < <(jq -r '(.trusted // {}) as $trusted | .enrolled // {} | to_entries[] | select($trusted[.key]) | "\(.key) \(.value.StoreTime)"' <<<"$state")
|
|
|
|
# A first setup that failed and then failed to undo itself can lose
|
|
# policy.json after switching Bolt off; Bolt's original config is then only
|
|
# in the setup journal. A deferred install that never finished setup left
|
|
# Bolt's mode untouched and has neither.
|
|
setup=$(sudo cat "$tb_dir/setup-recovery.json" 2>/dev/null || echo '{}')
|
|
config=$(jq -c '[.files // {} | to_entries[] | select(.key | endswith("/boltd.conf")) | .value]' <<<"$setup")
|
|
mode=$(jq -r '.original_authmode // empty' <<<"$state")
|
|
if [[ -z $mode && $config != "[]" ]]; then
|
|
mode=$(jq -r '.[0].data // ""' <<<"$config" | base64 -d | awk '
|
|
/^[[:space:]]*\[/ { section = ($0 ~ /^[[:space:]]*\[config\][[:space:]]*$/) }
|
|
section && /^[[:space:]]*AuthMode[[:space:]]*=/ { sub(/^[^=]*=[[:space:]]*/, ""); sub(/[[:space:]]*$/, ""); mode = $0 }
|
|
END { print mode == "" ? "enabled" : mode }
|
|
')
|
|
fi
|
|
if [[ -n $mode ]]; then
|
|
sudo boltctl config global.auth-mode "$mode"
|
|
fi
|
|
|
|
if (( unrestored )) && [[ -d $tb_dir ]]; then
|
|
sudo mv -T "$tb_dir" "$tb_dir.unrestored"
|
|
echo "The saved Thunderbolt settings are kept in $tb_dir.unrestored."
|
|
else
|
|
sudo rm -rf "$tb_dir"
|
|
fi
|
|
sudo rm -rf /run/omarchy-thunderbolt-authorization
|
|
fi
|
|
|
|
# limine-mkinitcpio reports success even when a kernel image fails to build, so
|
|
# check every current kernel entry. Limine's command line wins, except under
|
|
# Secure Boot, where the signed image's embedded one does.
|
|
usb_boot_cleared() {
|
|
local entries key value esp secure_boot=0 found=0
|
|
local efivar=/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
|
|
|
|
if [[ -r $efivar && $(od -An -t u1 -j 4 -N 1 "$efivar") == *1 ]]; then
|
|
secure_boot=1
|
|
fi
|
|
esp=$(sed -n 's/^ESP_PATH=["'\'']\?\([^"'\'']*\).*/\1/p' /etc/default/limine | tail -1)
|
|
esp=${esp:-/boot}
|
|
entries=$(sudo awk '
|
|
/^ \/\/[^\/]/ { entry = 1; next }
|
|
/^ *\// { entry = 0 }
|
|
entry && /^ (path|cmdline):/ { key = $1; sub(/^ [a-z]+:[[:space:]]*/, ""); print key "\t" $0 }
|
|
' "$esp/limine.conf") || return 1
|
|
|
|
while IFS=$'\t' read -r key value; do
|
|
if [[ $key == "cmdline:" ]]; then
|
|
[[ " $value " != *" $usb_boot_parameter "* ]] || return 1
|
|
elif [[ $key == "path:" ]]; then
|
|
found=1
|
|
if (( secure_boot )); then
|
|
value=${value#'boot():'}
|
|
value=$esp${value%%#*}
|
|
sudo test -r "$value" || return 1
|
|
if sudo grep -qaF "$usb_boot_parameter" "$value"; then
|
|
return 1
|
|
fi
|
|
fi
|
|
fi
|
|
done <<<"$entries"
|
|
(( found ))
|
|
}
|
|
|
|
# The opt-in USB at Boot setting: the parameter on the kernel command line, for
|
|
# new boot entries and new snapshots. Keep a marker until the rebuilt entries
|
|
# are verified, so an interrupted rebuild is retried rather than skipped.
|
|
if [[ -e $usb_boot_drop_in || -e $usb_boot_pending ]] || grep -Fqx '# Omarchy USB authorization begin' /etc/default/limine 2>/dev/null; then
|
|
sudo touch "$usb_boot_pending"
|
|
sudo rm -f "$usb_boot_drop_in"
|
|
sudo sed -i '/^# Omarchy USB authorization begin$/,/^# Omarchy USB authorization end$/d' /etc/default/limine
|
|
sudo limine-mkinitcpio
|
|
if ! usb_boot_cleared; then
|
|
echo "A boot entry still has $usb_boot_parameter, so USBGuard stays until it is rebuilt. Run omarchy-migrate to retry."
|
|
exit 1
|
|
fi
|
|
sudo rm -f "$usb_boot_pending"
|
|
fi
|
|
|
|
# Leave a USBGuard the user set up before Omarchy shipped it. pacman resets the
|
|
# install date on every upgrade, so ask its log when usbguard first arrived.
|
|
usbguard_from_omarchy() {
|
|
local first
|
|
|
|
if [[ -f $usb_state/policy-generated-by-omarchy ]]; then
|
|
return 0
|
|
fi
|
|
first=$(grep -m1 -oP '^\[\K[^]]+(?=\] \[ALPM\] installed usbguard )' /var/log/pacman.log) || return 1
|
|
(( $(date -d "$first" +%s) >= usbguard_shipped )) || return 1
|
|
[[ $(sudo cat "/etc/usbguard/IPCAccessControl.d/$USER" 2>/dev/null) == "$usbguard_grant" ]]
|
|
}
|
|
|
|
if omarchy-pkg-present usbguard && usbguard_from_omarchy; then
|
|
sudo systemctl disable --now usbguard.service
|
|
# USBGuard leaves the root hubs denying new devices when it exits, until
|
|
# the next boot. Restore the kernel default and authorize what it blocked.
|
|
shopt -s nullglob
|
|
echo 1 | sudo tee /sys/bus/usb/devices/usb*/authorized_default /sys/bus/usb/devices/*/authorized >/dev/null
|
|
shopt -u nullglob
|
|
omarchy-pkg-drop usbguard
|
|
|
|
# The approval access granted to each user outlives the package.
|
|
while IFS= read -r -d '' grant; do
|
|
if [[ $(sudo cat "$grant") == "$usbguard_grant" ]]; then
|
|
sudo rm -f "$grant"
|
|
fi
|
|
done < <(sudo find /etc/usbguard/IPCAccessControl.d -maxdepth 1 -type f -print0 2>/dev/null)
|
|
sudo rmdir /etc/usbguard/IPCAccessControl.d /etc/usbguard 2>/dev/null || true
|
|
fi
|
|
|
|
for action in /usr/share/polkit-1/actions/org.omarchy.usb.policy /usr/share/polkit-1/actions/org.omarchy.thunderbolt.policy; do
|
|
if [[ -e $action ]] && ! pacman -Qo "$action" &>/dev/null; then
|
|
sudo rm -f "$action"
|
|
fi
|
|
done
|
|
|
|
rm -rf "$usb_state"
|