Fully revert USB and Thunderbolt device approval (#15091)
* 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
This commit is contained in:
1 parent
9480d1b6a9
commit
defbf90e89
60 files changed
+557
-5986
No files matched your search
@@ -1,3 +0,0 @@
|
||||
echo "Enable approval for newly connected USB devices"
|
||||
|
||||
omarchy-setup-security-usb-authorization --yes
|
||||
@@ -1,2 +0,0 @@
|
||||
echo "Require approval for new Thunderbolt accessories"
|
||||
omarchy-setup-security-thunderbolt-authorization --quiet
|
||||
@@ -1,7 +0,0 @@
|
||||
echo "Repair USB approval prompts and enable portable device approvals"
|
||||
|
||||
# Preserve an explicit opt-out. Existing users with an active/enabled daemon
|
||||
# need a working watcher even if their old checkout link has become dangling.
|
||||
if systemctl is-enabled --quiet usbguard.service || systemctl is-active --quiet usbguard.service; then
|
||||
omarchy-setup-security-usb-authorization --yes
|
||||
fi
|
||||
@@ -0,0 +1,212 @@
|
||||
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"
|
||||
Reference in new issue
Block a user