Require approval for new USB devices by default
Co-authored-by: Outfoxxed <outfoxxed@outfoxxed.me>
This commit is contained in:
1 parent
86a2e5830e
commit
44f68f4329
19 files changed
+1382
-2
No files matched your search
@@ -747,6 +747,12 @@ create_user() {
|
||||
echo "%wheel ALL=(ALL:ALL) ALL" >/etc/sudoers.d/00-omarchy-wheel
|
||||
chmod 440 /etc/sudoers.d/00-omarchy-wheel
|
||||
|
||||
if omarchy-pkg-present usbguard; then
|
||||
source "$OMARCHY_PATH/install/helpers/usb-authorization.sh"
|
||||
usb_authorization_add_user "$username"
|
||||
systemctl try-restart usbguard.service
|
||||
fi
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/browser-policy.sh"
|
||||
for dir in "${BROWSER_POLICY_MANAGED_DIRS[@]}"; do
|
||||
[[ -d $dir || -L $dir ]] || continue
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Stop asking before new USB devices can access the system
|
||||
# omarchy:args=[--boot-only] [--yes]
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
assume_yes=0
|
||||
boot_only=0
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--yes) assume_yes=1 ;;
|
||||
--boot-only) boot_only=1 ;;
|
||||
-h | --help)
|
||||
echo "Usage: omarchy-remove-security-usb-authorization [--boot-only] [--yes]"
|
||||
echo
|
||||
echo "Disables Omarchy's USB approval prompts and restores the kernel's"
|
||||
echo "normal default-allow behavior."
|
||||
echo "Use --boot-only to keep USBGuard enabled after the system starts."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "omarchy-remove-security-usb-authorization: unknown option '$1'. Try --help." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if (( boot_only )); then
|
||||
if (( ! assume_yes )); then
|
||||
gum style --foreground 212 --bold "Remove USB authorization from boot"
|
||||
echo
|
||||
echo "USBGuard will continue blocking unknown devices after its service starts."
|
||||
echo "Devices can reach kernel drivers earlier during boot."
|
||||
echo
|
||||
gum confirm "Disable USB authorization from kernel boot?" || exit 130
|
||||
fi
|
||||
|
||||
sudo omarchy-usb-authorization-boot disable
|
||||
echo
|
||||
echo "Boot-time USB authorization is disabled and takes effect after reboot."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
state_dir="$HOME/.local/state/omarchy/usb-authorization"
|
||||
unit="$HOME/.config/systemd/user/omarchy-usb-authorization.service"
|
||||
|
||||
if (( ! assume_yes )); then
|
||||
gum style --foreground 212 --bold "Remove USB device authorization"
|
||||
echo
|
||||
echo "New USB devices will be allowed to bind to kernel drivers without approval."
|
||||
echo
|
||||
gum confirm "Disable USB device authorization?" || exit 130
|
||||
fi
|
||||
|
||||
# Rebuild a permissive boot image before stopping USBGuard. If rebuilding
|
||||
# fails, the command stops here and leaves the complete policy enabled.
|
||||
sudo omarchy-usb-authorization-boot disable
|
||||
|
||||
systemctl --user disable --now omarchy-usb-authorization.service 2>/dev/null || true
|
||||
rm -f "$unit"
|
||||
systemctl --user daemon-reload
|
||||
|
||||
if omarchy-pkg-present usbguard; then
|
||||
sudo usbguard remove-user "$USER" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if omarchy-pkg-present usbguard; then
|
||||
sudo systemctl disable --now usbguard.service 2>/dev/null || true
|
||||
sudo omarchy-usb-authorization-restore-default
|
||||
fi
|
||||
|
||||
if [[ -d $state_dir/requests ]]; then
|
||||
find "$state_dir/requests" -mindepth 1 -maxdepth 1 -type f -delete
|
||||
rmdir "$state_dir/requests" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
rm -f "$state_dir/package-installed-by-omarchy" \
|
||||
"$state_dir/policy-generated-by-omarchy" \
|
||||
"$state_dir/service-enabled-by-omarchy"
|
||||
rmdir "$state_dir" 2>/dev/null || true
|
||||
|
||||
echo "USB device authorization is disabled."
|
||||
Executable
+112
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Ask before new USB devices can access the system
|
||||
# omarchy:args=[--boot] [--yes]
|
||||
# omarchy:requires-sudo=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
assume_yes=0
|
||||
from_boot=0
|
||||
|
||||
while (( $# > 0 )); do
|
||||
case "$1" in
|
||||
--yes) assume_yes=1 ;;
|
||||
--boot) from_boot=1 ;;
|
||||
-h | --help)
|
||||
echo "Usage: omarchy-setup-security-usb-authorization [--boot] [--yes]"
|
||||
echo
|
||||
echo "Trusts USB devices that are connected now, then blocks new devices"
|
||||
echo "until you approve them from an Omarchy notification."
|
||||
echo
|
||||
echo "--boot also blocks USB devices from the start of kernel boot. Connected"
|
||||
echo "devices are added to USBGuard's policy, but remain unavailable before"
|
||||
echo "USBGuard starts, including at an encrypted-disk password prompt."
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "omarchy-setup-security-usb-authorization: unknown option '$1'. Try --help." >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
state_dir="$HOME/.local/state/omarchy/usb-authorization"
|
||||
unit_source="$OMARCHY_PATH/default/systemd/user/omarchy-usb-authorization.service"
|
||||
unit="$HOME/.config/systemd/user/omarchy-usb-authorization.service"
|
||||
rules_file=/etc/usbguard/rules.conf
|
||||
daemon_config=/etc/usbguard/usbguard-daemon.conf
|
||||
|
||||
source "$OMARCHY_PATH/install/helpers/usb-authorization.sh"
|
||||
|
||||
if (( ! assume_yes )); then
|
||||
if (( from_boot )); then
|
||||
gum style --foreground 212 --bold "USB authorization from boot"
|
||||
else
|
||||
gum style --foreground 212 --bold "USB device authorization"
|
||||
fi
|
||||
echo
|
||||
echo "Devices connected now will be trusted. Disconnect anything you do not trust."
|
||||
echo "New USB devices will remain blocked until you approve them."
|
||||
if (( from_boot )); then
|
||||
echo
|
||||
echo "Boot-time protection blocks all USB devices until USBGuard starts."
|
||||
echo "A USB keyboard will not work at an encrypted-disk password prompt, even"
|
||||
echo "when it is connected now. Continue only if unlock and recovery do not"
|
||||
echo "depend on USB input."
|
||||
fi
|
||||
echo
|
||||
if (( from_boot )); then
|
||||
gum confirm "Enable USB authorization from kernel boot?" || exit 130
|
||||
else
|
||||
gum confirm "Enable USB device authorization?" || exit 130
|
||||
fi
|
||||
fi
|
||||
|
||||
omarchy-pkg-add usbguard
|
||||
|
||||
usb_authorization_require_secure_settings "$daemon_config"
|
||||
|
||||
mkdir -p "$state_dir" "${unit%/*}"
|
||||
chmod 700 "$state_dir"
|
||||
|
||||
if ! usb_authorization_rules_present "$rules_file"; then
|
||||
policy=$(mktemp "${TMPDIR:-/tmp}/omarchy-usb-policy.XXXXXXXXXX")
|
||||
trap 'rm -f "$policy"' EXIT
|
||||
chmod 600 "$policy"
|
||||
usb_authorization_generate_policy "$policy"
|
||||
sudo install -Dm600 -o root -g root "$policy" "$rules_file"
|
||||
touch "$state_dir/policy-generated-by-omarchy"
|
||||
fi
|
||||
|
||||
# This user can inspect USBGuard events and approve the device named by an
|
||||
# event. Policy and daemon parameters remain unavailable through the IPC ACL.
|
||||
sudo usbguard add-user "$USER" \
|
||||
--devices=list,listen,modify \
|
||||
--policy=list \
|
||||
--exceptions=listen
|
||||
|
||||
sudo systemctl enable usbguard.service
|
||||
# add-user ACL changes are loaded only when the daemon starts, so restart an
|
||||
# existing service as well as starting a fresh one.
|
||||
sudo systemctl restart usbguard.service
|
||||
|
||||
ln -sfn "$unit_source" "$unit"
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now omarchy-usb-authorization.service
|
||||
|
||||
if (( from_boot )); then
|
||||
# Take the trust snapshot only after USBGuard is enforcing the policy. Every
|
||||
# ID is resolved by the daemon, and no device-controlled descriptor text is
|
||||
# passed to a privileged command.
|
||||
usb_authorization_trust_present_devices
|
||||
sudo omarchy-usb-authorization-boot enable
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "USB device authorization is enabled."
|
||||
echo "Devices connected during setup remain trusted; new devices require approval."
|
||||
if (( from_boot )); then
|
||||
echo "Boot-time authorization is enabled and takes effect after reboot."
|
||||
fi
|
||||
Executable
+432
@@ -0,0 +1,432 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Manage USB authorization from the start of kernel boot
|
||||
# omarchy:args=<enable|disable>
|
||||
# omarchy:requires-sudo=true
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
drop_in=/etc/limine-entry-tool.d/usb-authorization.conf
|
||||
setting='KERNEL_CMDLINE[default]+=" usbcore.authorized_default=0"'
|
||||
limine_conf=/boot/limine.conf
|
||||
limine_defaults=/etc/default/limine
|
||||
boot_path=/boot
|
||||
kernel_parameter=usbcore.authorized_default=0
|
||||
snapshot_begin='# Omarchy USB authorization begin'
|
||||
snapshot_setting='SNAPSHOT_KERNEL_PARAMETERS+=usbcore.authorized_default=0'
|
||||
snapshot_end='# Omarchy USB authorization end'
|
||||
|
||||
usb_authorization_cmdline_has_parameter() {
|
||||
local cmdline="$1" parameter="$2" word
|
||||
|
||||
for word in $cmdline; do
|
||||
[[ $word == "$parameter" ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
usb_authorization_secure_boot_enabled() {
|
||||
local value variable
|
||||
|
||||
for variable in /sys/firmware/efi/efivars/SecureBoot-*; do
|
||||
[[ -r $variable ]] || continue
|
||||
value=$(od -An -t u1 -j 4 -N 1 "$variable")
|
||||
value=${value//[[:space:]]/}
|
||||
[[ $value == "1" ]] && return 0
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
usb_authorization_verify_boot_entries() {
|
||||
local expected="$1" config="$2" esp="$3"
|
||||
local machine_id="${4:-$(</etc/machine-id)}"
|
||||
local secure_boot="${5:-auto}"
|
||||
local actual_hash embedded_cmdline expected_hash image mode path paths section
|
||||
|
||||
if [[ $secure_boot == "auto" ]]; then
|
||||
if usb_authorization_secure_boot_enabled; then
|
||||
secure_boot=enabled
|
||||
else
|
||||
secure_boot=disabled
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -r $config ]]; then
|
||||
echo "Cannot verify the generated Limine boot entries in $config." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
paths=$(mktemp /tmp/omarchy-usb-boot-paths.XXXXXXXXXX)
|
||||
section=$(mktemp /tmp/omarchy-usb-boot-cmdline.XXXXXXXXXX)
|
||||
|
||||
if ! awk -v expected="$expected" -v machine_id="$machine_id" -v parameter="$kernel_parameter" '
|
||||
function contains_parameter(value, count, fields, field_index) {
|
||||
count = split(value, fields, /[[:space:]]+/)
|
||||
for (field_index = 1; field_index <= count; field_index++) {
|
||||
if (fields[field_index] == parameter) return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function finish_entry(found) {
|
||||
if (!target_os || !is_kernel) return
|
||||
entries++
|
||||
|
||||
if (has_cmdline) {
|
||||
found = contains_parameter(cmdline)
|
||||
if ((expected == "enabled" && !found) || (expected == "disabled" && found)) bad = 1
|
||||
}
|
||||
|
||||
if (path == "") {
|
||||
bad = 1
|
||||
} else {
|
||||
print (has_cmdline ? "external" : "embedded") "\t" path
|
||||
}
|
||||
}
|
||||
|
||||
/^\/[^/]/ {
|
||||
finish_entry()
|
||||
target_os = 0
|
||||
is_kernel = 0
|
||||
has_cmdline = 0
|
||||
cmdline = ""
|
||||
path = ""
|
||||
next
|
||||
}
|
||||
|
||||
/^[[:space:]]*comment:[[:space:]]*machine-id=/ {
|
||||
target_os = index($0, "machine-id=" machine_id) > 0
|
||||
next
|
||||
}
|
||||
|
||||
/^[[:space:]]*\/\// {
|
||||
finish_entry()
|
||||
is_kernel = 0
|
||||
has_cmdline = 0
|
||||
cmdline = ""
|
||||
path = ""
|
||||
next
|
||||
}
|
||||
|
||||
/^[[:space:]]*comment:[[:space:]]*kernel-id=/ {
|
||||
is_kernel = target_os
|
||||
next
|
||||
}
|
||||
|
||||
is_kernel && /^[[:space:]]*path:[[:space:]]*/ {
|
||||
path = $0
|
||||
sub(/^[[:space:]]*path:[[:space:]]*/, "", path)
|
||||
next
|
||||
}
|
||||
|
||||
is_kernel && /^[[:space:]]*cmdline:[[:space:]]*/ {
|
||||
has_cmdline = 1
|
||||
cmdline = $0
|
||||
sub(/^[[:space:]]*cmdline:[[:space:]]*/, "", cmdline)
|
||||
next
|
||||
}
|
||||
|
||||
END {
|
||||
finish_entry()
|
||||
if (entries == 0) bad = 1
|
||||
exit bad
|
||||
}
|
||||
' "$config" >"$paths"; then
|
||||
rm -f "$paths" "$section"
|
||||
echo "The generated Limine kernel entries do not all have USB authorization $expected." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
while IFS=$'\t' read -r mode path; do
|
||||
if [[ $path != 'boot():/'* ]]; then
|
||||
rm -f "$paths" "$section"
|
||||
echo "Cannot verify the kernel image referenced by Limine: $path" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
image="$esp/${path#'boot():/'}"
|
||||
image=${image%%#*}
|
||||
if [[ ! -r $image ]]; then
|
||||
rm -f "$paths" "$section"
|
||||
echo "Cannot verify the kernel image referenced by Limine: $image" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ $path == *'#'* ]]; then
|
||||
expected_hash=${path##*#}
|
||||
actual_hash=$(b2sum "$image")
|
||||
actual_hash=${actual_hash%% *}
|
||||
if [[ $actual_hash != "$expected_hash" ]]; then
|
||||
rm -f "$paths" "$section"
|
||||
echo "The verification hash for $image does not match the generated Limine entry." >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
: >"$section"
|
||||
if ! objcopy -O binary --only-section=.cmdline "$image" "$section" 2>/dev/null; then
|
||||
[[ $mode == "external" ]] && continue
|
||||
rm -f "$paths" "$section"
|
||||
echo "Cannot inspect the embedded kernel command line in $image." >&2
|
||||
return 1
|
||||
fi
|
||||
embedded_cmdline=$(tr '\0' '\n' <"$section")
|
||||
|
||||
# Without Secure Boot, Limine's external command line takes precedence.
|
||||
# Under Secure Boot, systemd-stub uses a nonempty embedded .cmdline.
|
||||
if [[ $mode == "external" && $secure_boot == "disabled" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# A signed UKI can ignore Limine's external cmdline and use its embedded
|
||||
# .cmdline instead. If one exists, both representations must agree.
|
||||
if [[ -z $embedded_cmdline ]]; then
|
||||
if [[ $mode == "external" || $expected == "disabled" ]]; then
|
||||
continue
|
||||
fi
|
||||
rm -f "$paths" "$section"
|
||||
echo "The generated kernel image $image has no verifiable kernel command line." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ $expected == "enabled" ]]; then
|
||||
if ! usb_authorization_cmdline_has_parameter "$embedded_cmdline" "$kernel_parameter"; then
|
||||
rm -f "$paths" "$section"
|
||||
echo "The generated kernel image $image does not enable boot-time USB authorization." >&2
|
||||
return 1
|
||||
fi
|
||||
elif usb_authorization_cmdline_has_parameter "$embedded_cmdline" "$kernel_parameter"; then
|
||||
rm -f "$paths" "$section"
|
||||
echo "The generated kernel image $image still enables boot-time USB authorization." >&2
|
||||
return 1
|
||||
fi
|
||||
done <"$paths"
|
||||
|
||||
rm -f "$paths" "$section"
|
||||
}
|
||||
|
||||
usb_authorization_rebuild_and_verify() {
|
||||
local expected="$1" config="$2" esp="$3"
|
||||
local machine_id="${4:-$(</etc/machine-id)}" secure_boot="${5:-auto}"
|
||||
|
||||
if ! limine-mkinitcpio; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# limine-mkinitcpio-hook can swallow an individual kernel build failure and
|
||||
# still return success, so its exit status alone is not a safety boundary.
|
||||
usb_authorization_verify_boot_entries "$expected" "$config" "$esp" "$machine_id" "$secure_boot"
|
||||
}
|
||||
|
||||
usb_authorization_snapshot_block_status() {
|
||||
local config="$1"
|
||||
|
||||
[[ -r $config ]] || return 2
|
||||
awk -v begin="$snapshot_begin" -v setting="$snapshot_setting" -v end="$snapshot_end" '
|
||||
$0 == begin { begin_count++; state = 1; next }
|
||||
$0 == setting { setting_count++; if (state == 1) state = 2; next }
|
||||
$0 == end { end_count++; if (state == 2) valid_count++; state = 0; next }
|
||||
{ state = 0 }
|
||||
END {
|
||||
if (begin_count == 0 && setting_count == 0 && end_count == 0) exit 1
|
||||
if (begin_count == 1 && setting_count == 1 && end_count == 1 && valid_count == 1) exit 0
|
||||
exit 2
|
||||
}
|
||||
' "$config"
|
||||
}
|
||||
|
||||
usb_authorization_enable_snapshot_setting() {
|
||||
local config="$1" status temporary
|
||||
|
||||
if usb_authorization_snapshot_block_status "$config"; then
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
if (( status != 1 )); then
|
||||
echo "Refusing to modify the USB authorization block in $config." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
temporary=$(mktemp "${config}.XXXXXXXXXX")
|
||||
cp --preserve=mode,ownership "$config" "$temporary"
|
||||
{
|
||||
cat "$config"
|
||||
printf '\n%s\n%s\n%s\n' "$snapshot_begin" "$snapshot_setting" "$snapshot_end"
|
||||
} >"$temporary"
|
||||
mv -f "$temporary" "$config"
|
||||
}
|
||||
|
||||
usb_authorization_disable_snapshot_setting() {
|
||||
local config="$1" status temporary
|
||||
|
||||
if usb_authorization_snapshot_block_status "$config"; then
|
||||
status=0
|
||||
else
|
||||
status=$?
|
||||
fi
|
||||
(( status == 1 )) && return 0
|
||||
if (( status != 0 )); then
|
||||
echo "Refusing to modify the USB authorization block in $config." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
temporary=$(mktemp "${config}.XXXXXXXXXX")
|
||||
cp --preserve=mode,ownership "$config" "$temporary"
|
||||
awk -v begin="$snapshot_begin" '
|
||||
$0 == begin { skip = 2; next }
|
||||
skip > 0 { skip--; next }
|
||||
{ print }
|
||||
' "$config" >"$temporary"
|
||||
mv -f "$temporary" "$config"
|
||||
}
|
||||
|
||||
usb_authorization_rewrite_boot_cmdlines() {
|
||||
local expected="$1" config="$2" machine_id="${3:-$(</etc/machine-id)}"
|
||||
local backup temporary
|
||||
|
||||
backup=$(mktemp "${config}.omarchy-usb-backup.XXXXXXXXXX")
|
||||
temporary=$(mktemp "${config}.omarchy-usb-new.XXXXXXXXXX")
|
||||
cp --preserve=mode,ownership "$config" "$backup"
|
||||
cp --preserve=mode,ownership "$config" "$temporary"
|
||||
|
||||
if ! awk -v expected="$expected" -v machine_id="$machine_id" -v parameter="$kernel_parameter" '
|
||||
function rewrite_cmdline(line, prefix, value, count, fields, field_index, result, found) {
|
||||
prefix = line
|
||||
sub(/cmdline:.*/, "cmdline:", prefix)
|
||||
value = line
|
||||
sub(/^[[:space:]]*cmdline:[[:space:]]*/, "", value)
|
||||
count = split(value, fields, /[[:space:]]+/)
|
||||
result = ""
|
||||
found = 0
|
||||
for (field_index = 1; field_index <= count; field_index++) {
|
||||
if (fields[field_index] == parameter) {
|
||||
found = 1
|
||||
continue
|
||||
}
|
||||
if (fields[field_index] != "") result = result (result == "" ? "" : " ") fields[field_index]
|
||||
}
|
||||
if (expected == "enabled") result = result (result == "" ? "" : " ") parameter
|
||||
return prefix (result == "" ? "" : " ") result
|
||||
}
|
||||
|
||||
/^\/[^/]/ { target_os = 0 }
|
||||
/^[[:space:]]*comment:[[:space:]]*machine-id=/ {
|
||||
target_os = index($0, "machine-id=" machine_id) > 0
|
||||
}
|
||||
target_os && /^[[:space:]]*cmdline:[[:space:]]*/ {
|
||||
print rewrite_cmdline($0)
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$config" >"$temporary"; then
|
||||
rm -f "$backup" "$temporary"
|
||||
return 1
|
||||
fi
|
||||
|
||||
mv -f "$temporary" "$config"
|
||||
if ! limine-enroll-config; then
|
||||
mv -f "$backup" "$config"
|
||||
limine-enroll-config >/dev/null 2>&1 || true
|
||||
echo "Restored $config because its bootloader verification data could not be updated." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -f "$backup"
|
||||
}
|
||||
|
||||
main() {
|
||||
local had_drop_in=0 had_snapshot_setting=0 snapshot_status temporary
|
||||
|
||||
if (( EUID != 0 )); then
|
||||
echo "Error: omarchy-usb-authorization-boot must run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
case "${1:-}" in
|
||||
enable)
|
||||
if usb_authorization_snapshot_block_status "$limine_defaults"; then
|
||||
snapshot_status=0
|
||||
else
|
||||
snapshot_status=$?
|
||||
fi
|
||||
if (( snapshot_status == 2 )); then
|
||||
echo "Refusing to modify the USB authorization block in $limine_defaults." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Quote the expected value because the Limine key contains glob brackets.
|
||||
if [[ -e $drop_in ]] && [[ $(<"$drop_in") != "$setting" ]]; then
|
||||
echo "Refusing to replace the existing $drop_in." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usb_authorization_enable_snapshot_setting "$limine_defaults"
|
||||
|
||||
if [[ ! -e $drop_in ]]; then
|
||||
install -d -m755 "${drop_in%/*}"
|
||||
temporary=$(mktemp "${drop_in}.XXXXXXXXXX")
|
||||
trap 'rm -f "$temporary"' EXIT
|
||||
printf '%s\n' "$setting" >"$temporary"
|
||||
chmod 644 "$temporary"
|
||||
mv -f "$temporary" "$drop_in"
|
||||
trap - EXIT
|
||||
fi
|
||||
|
||||
if ! limine-mkinitcpio ||
|
||||
! usb_authorization_rewrite_boot_cmdlines enabled "$limine_conf" ||
|
||||
! usb_authorization_verify_boot_entries enabled "$limine_conf" "$boot_path"; then
|
||||
echo "Boot-time USB authorization remains configured in $drop_in; rerun this command after fixing the boot-image error." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
disable)
|
||||
if usb_authorization_snapshot_block_status "$limine_defaults"; then
|
||||
had_snapshot_setting=1
|
||||
else
|
||||
snapshot_status=$?
|
||||
if (( snapshot_status == 2 )); then
|
||||
echo "Refusing to modify the USB authorization block in $limine_defaults." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -e $drop_in ]]; then
|
||||
if [[ $(<"$drop_in") != "$setting" ]]; then
|
||||
echo "Refusing to remove the modified $drop_in." >&2
|
||||
exit 1
|
||||
fi
|
||||
had_drop_in=1
|
||||
rm -f "$drop_in"
|
||||
elif (( ! had_snapshot_setting )) && usb_authorization_verify_boot_entries disabled "$limine_conf" "$boot_path"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
usb_authorization_disable_snapshot_setting "$limine_defaults"
|
||||
if ! limine-mkinitcpio ||
|
||||
! usb_authorization_rewrite_boot_cmdlines disabled "$limine_conf" ||
|
||||
! usb_authorization_verify_boot_entries disabled "$limine_conf" "$boot_path"; then
|
||||
if (( had_drop_in )); then
|
||||
printf '%s\n' "$setting" >"$drop_in"
|
||||
chmod 644 "$drop_in"
|
||||
fi
|
||||
if (( had_snapshot_setting )); then
|
||||
usb_authorization_enable_snapshot_setting "$limine_defaults"
|
||||
fi
|
||||
echo "USBGuard remains required because the boot images could not be verified as default-allow." >&2
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: omarchy-usb-authorization-boot <enable|disable>" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
|
||||
main "$@"
|
||||
fi
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:hidden=true
|
||||
# omarchy:summary=Create an approval request for a blocked USB device
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
[[ ${USBGUARD_IPC_SIGNAL:-} == "Device.PresenceChanged" ]] || exit 0
|
||||
[[ ${USBGUARD_DEVICE_EVENT:-} == "Insert" ]] || exit 0
|
||||
[[ ${USBGUARD_DEVICE_TARGET:-} == "block" ]] || exit 0
|
||||
[[ ${USBGUARD_DEVICE_ID:-} =~ ^[0-9]+$ ]] || exit 0
|
||||
[[ -n ${USBGUARD_DEVICE_RULE:-} ]] || exit 0
|
||||
|
||||
requests="$HOME/.local/state/omarchy/usb-authorization/requests"
|
||||
mkdir -p "$requests"
|
||||
chmod 700 "${requests%/*}" "$requests"
|
||||
|
||||
# The token is safe to carry through the presentation launcher's command
|
||||
# string. The device-controlled rule itself stays in a mode-0600 JSON file and
|
||||
# is never interpolated into a shell command.
|
||||
token="request-$(printf '%s\0%s' "$USBGUARD_DEVICE_ID" "$USBGUARD_DEVICE_RULE" | sha256sum | cut -d' ' -f1)"
|
||||
request="$requests/$token.json"
|
||||
temporary=$(mktemp "$requests/.request.XXXXXXXXXX")
|
||||
trap 'rm -f "$temporary"' EXIT
|
||||
|
||||
jq -n \
|
||||
--arg id "$USBGUARD_DEVICE_ID" \
|
||||
--arg rule "$USBGUARD_DEVICE_RULE" \
|
||||
'{id: $id, rule: $rule}' >"$temporary"
|
||||
chmod 600 "$temporary"
|
||||
mv -f "$temporary" "$request"
|
||||
|
||||
omarchy-notification-send \
|
||||
--app-name omarchy-action \
|
||||
--urgency critical \
|
||||
--glyph \
|
||||
"USB accessory blocked" \
|
||||
"Click to review it. Keep it blocked unless you recognize the device." \
|
||||
--exec omarchy-launch-floating-terminal-with-presentation \
|
||||
omarchy-usb-authorization-review "$token"
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:summary=Restore the kernel's default USB authorization state
|
||||
# omarchy:requires-sudo=true
|
||||
# omarchy:hidden=true
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if (( EUID != 0 )); then
|
||||
echo "Error: omarchy-usb-authorization-restore-default must run as root" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# USBGuard intentionally leaves root hubs in default-deny mode when it exits.
|
||||
# Restore both the hub defaults and devices that are still connected.
|
||||
shopt -s nullglob
|
||||
for attribute in /sys/bus/usb/devices/usb*/authorized_default; do
|
||||
echo 1 >"$attribute"
|
||||
done
|
||||
for attribute in /sys/bus/usb/devices/*/authorized; do
|
||||
echo 1 >"$attribute"
|
||||
done
|
||||
shopt -u nullglob
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:hidden=true
|
||||
# omarchy:summary=Review a blocked USB device authorization request
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
token="${1:-}"
|
||||
if [[ ! $token =~ ^request-[0-9a-f]{64}$ ]]; then
|
||||
echo "This USB authorization request is invalid or expired." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
request="$HOME/.local/state/omarchy/usb-authorization/requests/$token.json"
|
||||
if [[ ! -f $request || -L $request ]]; then
|
||||
echo "This USB authorization request is no longer available." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
id=$(jq -er '.id | select(test("^[0-9]+$"))' "$request")
|
||||
expected_rule=$(jq -er '.rule | select(type == "string" and length > 0)' "$request")
|
||||
read_blocked_rule() {
|
||||
local line
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ $line == "$id: "* ]]; then
|
||||
printf '%s\n' "${line#"$id: "}"
|
||||
return 0
|
||||
fi
|
||||
done < <(usbguard list-devices --blocked)
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
current_rule=$(read_blocked_rule || true)
|
||||
|
||||
if [[ -z $current_rule || $current_rule != "$expected_rule" ]]; then
|
||||
rm -f "$request"
|
||||
echo "That USB device was removed or changed. Connect it again to review it." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
gum style --foreground 212 --bold "Blocked USB accessory"
|
||||
echo
|
||||
echo "The following details come from the device and can be forged:"
|
||||
printf '%s\n' "$current_rule" | LC_ALL=C tr -c '\11\12\15\40-\176' '?'
|
||||
echo
|
||||
|
||||
choice=$(gum choose \
|
||||
"Allow once" \
|
||||
"Always allow this device" \
|
||||
"Keep blocked") || exit 130
|
||||
|
||||
if [[ $choice != "Keep blocked" ]]; then
|
||||
current_rule=$(read_blocked_rule || true)
|
||||
if [[ -z $current_rule || $current_rule != "$expected_rule" ]]; then
|
||||
rm -f "$request"
|
||||
echo "That USB device was removed or changed. Connect it again to review it." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
case "$choice" in
|
||||
"Allow once")
|
||||
# Target the complete identity rule instead of USBGuard's numeric ID. IDs can
|
||||
# be reassigned when the daemon restarts while the review dialog is open.
|
||||
usbguard allow-device "$expected_rule"
|
||||
echo "USB accessory allowed until it is disconnected."
|
||||
;;
|
||||
"Always allow this device")
|
||||
usbguard allow-device --permanent "$expected_rule"
|
||||
echo "USB accessory added to the trusted policy."
|
||||
;;
|
||||
"Keep blocked")
|
||||
echo "USB accessory remains blocked."
|
||||
;;
|
||||
esac
|
||||
|
||||
rm -f "$request"
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# omarchy:hidden=true
|
||||
# omarchy:summary=Watch USBGuard for devices awaiting user approval
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
event_handler="$OMARCHY_PATH/bin/omarchy-usb-authorization-event"
|
||||
|
||||
# A device inserted while the graphical session was stopped is already safely
|
||||
# blocked by USBGuard. Surface it now so the user can approve it after login.
|
||||
while IFS= read -r line; do
|
||||
if [[ $line =~ ^([0-9]+):[[:space:]](block.*)$ ]]; then
|
||||
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
|
||||
USBGUARD_DEVICE_ID="${BASH_REMATCH[1]}" \
|
||||
USBGUARD_DEVICE_EVENT=Insert \
|
||||
USBGUARD_DEVICE_TARGET=block \
|
||||
USBGUARD_DEVICE_RULE="${BASH_REMATCH[2]}" \
|
||||
"$event_handler"
|
||||
fi
|
||||
done < <(usbguard list-devices --blocked)
|
||||
|
||||
exec usbguard watch --wait --exec "$event_handler"
|
||||
@@ -181,6 +181,8 @@
|
||||
"setup.config": {"icon":"","label":"Config"},
|
||||
"setup.security.fingerprint": {"icon":"","label":"Fingerprint","when":"omarchy-hw-fingerprint","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint"},
|
||||
"setup.security.fido2": {"icon":"","label":"Fido2","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fido2"},
|
||||
"setup.security.usb-authorization": {"icon":"","label":"USB Device Authorization","checked":"systemctl is-enabled --quiet usbguard.service && systemctl --user is-enabled --quiet omarchy-usb-authorization.service","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-usb-authorization"},
|
||||
"setup.security.usb-authorization-boot": {"icon":"","label":"USB at Boot","checked":"grep -Fqx 'KERNEL_CMDLINE[default]+=\" usbcore.authorized_default=0\"' /etc/limine-entry-tool.d/usb-authorization.conf 2>/dev/null","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-setup-security-usb-authorization --boot'"},
|
||||
"setup.security.sshd": {"icon":"","label":"SSHD","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sshd"},
|
||||
"setup.security.passwordless-sudo": {"icon":"","label":"Passwordless Sudo","action":"omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless"},
|
||||
"setup.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-sudoless-docker"},
|
||||
@@ -298,6 +300,8 @@
|
||||
"remove.security": {"icon":"","label":"Security","title":"Remove"},
|
||||
"remove.security.fingerprint": {"icon":"","label":"Fingerprint","when":"omarchy-pkg-present fprintd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fingerprint"},
|
||||
"remove.security.fido2": {"icon":"","label":"Fido2","when":"omarchy-pkg-present pam-u2f","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-fido2"},
|
||||
"remove.security.usb-authorization": {"icon":"","label":"USB Device Authorization","when":"systemctl is-enabled --quiet usbguard.service","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-usb-authorization"},
|
||||
"remove.security.usb-authorization-boot": {"icon":"","label":"USB at Boot","when":"grep -Fqx 'KERNEL_CMDLINE[default]+=\" usbcore.authorized_default=0\"' /etc/limine-entry-tool.d/usb-authorization.conf 2>/dev/null","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-remove-security-usb-authorization --boot-only'"},
|
||||
"remove.security.sshd": {"icon":"","label":"SSHD","when":"systemctl is-enabled --quiet sshd","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sshd"},
|
||||
"remove.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"! omarchy-sudo-docker --configured","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-security-sudoless-docker"},
|
||||
"remove.ai.hermes": {"icon":"","iconFont":"omarchy","label":"Hermes Desktop","when":"omarchy-pkg-present hermes-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-ai-hermes"},
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=Ask before allowing new USB devices
|
||||
After=graphical-session.target dbus.socket
|
||||
Requires=dbus.socket
|
||||
PartOf=graphical-session.target
|
||||
ConditionEnvironment=OMARCHY_PATH
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecCondition=/usr/bin/systemctl is-active --quiet usbguard.service
|
||||
ExecStart=/usr/bin/bash -lc 'exec "$OMARCHY_PATH/bin/omarchy-usb-authorization-watch"'
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -7,5 +7,6 @@ run_logged "$OMARCHY_INSTALL/config/ssh-command-path.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/ssh-keepalive.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/docker.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/snapper.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/usb-authorization.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/enable-services.sh"
|
||||
run_logged "$OMARCHY_INSTALL/config/firewall.sh"
|
||||
@@ -0,0 +1,28 @@
|
||||
echo "Configure USB device authorization"
|
||||
|
||||
rules_file="${OMARCHY_USB_AUTHORIZATION_RULES_FILE:-/etc/usbguard/rules.conf}"
|
||||
daemon_config="${OMARCHY_USB_AUTHORIZATION_DAEMON_CONFIG:-/etc/usbguard/usbguard-daemon.conf}"
|
||||
|
||||
source "$OMARCHY_INSTALL/helpers/usb-authorization.sh"
|
||||
usb_authorization_require_secure_settings "$daemon_config"
|
||||
|
||||
if [[ ! -s $rules_file ]]; then
|
||||
policy=$(mktemp "${TMPDIR:-/tmp}/omarchy-usb-policy.XXXXXXXXXX")
|
||||
chmod 600 "$policy"
|
||||
|
||||
if usb_authorization_generate_policy "$policy"; then
|
||||
install -Dm600 -o root -g root "$policy" "$rules_file"
|
||||
rm -f "$policy"
|
||||
else
|
||||
rm -f "$policy"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -n ${OMARCHY_INSTALL_USER:-} ]]; then
|
||||
usb_authorization_add_user "$OMARCHY_INSTALL_USER"
|
||||
fi
|
||||
|
||||
# Installs are followed by a reboot, so enable the daemon without starting it
|
||||
# in the live ISO's chroot.
|
||||
systemctl enable usbguard.service
|
||||
@@ -0,0 +1,99 @@
|
||||
usb_authorization_config_value() {
|
||||
local config="$1" key="$2"
|
||||
local awk_command=(awk)
|
||||
|
||||
if [[ ! -r $config ]]; then
|
||||
if [[ $config == "/etc/usbguard/usbguard-daemon.conf" ]]; then
|
||||
awk_command=(sudo awk)
|
||||
else
|
||||
echo "Cannot read USBGuard configuration: $config" >&2
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
"${awk_command[@]}" -F= -v key="$key" '
|
||||
/^[[:space:]]*#/ { next }
|
||||
$1 ~ "^[[:space:]]*" key "[[:space:]]*$" {
|
||||
value=$0
|
||||
sub(/^[^=]*=[[:space:]]*/, "", value)
|
||||
sub(/[[:space:]]*$/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' "$config"
|
||||
}
|
||||
|
||||
usb_authorization_rules_present() {
|
||||
local rules="$1"
|
||||
|
||||
if [[ -r $rules ]]; then
|
||||
[[ -s $rules ]]
|
||||
elif [[ $rules == "/etc/usbguard/rules.conf" ]]; then
|
||||
sudo test -s "$rules"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
usb_authorization_require_secure_setting() {
|
||||
local config="$1" key="$2" expected="$3" actual
|
||||
|
||||
actual=$(usb_authorization_config_value "$config" "$key")
|
||||
if [[ $actual != "$expected" ]]; then
|
||||
echo "USBGuard's $key must be '$expected' before Omarchy enables it." >&2
|
||||
echo "Current value: ${actual:-<unset>} in $config" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
usb_authorization_require_secure_settings() {
|
||||
local config="$1"
|
||||
|
||||
usb_authorization_require_secure_setting "$config" ImplicitPolicyTarget block
|
||||
usb_authorization_require_secure_setting "$config" PresentDevicePolicy apply-policy
|
||||
usb_authorization_require_secure_setting "$config" InsertedDevicePolicy apply-policy
|
||||
usb_authorization_require_secure_setting "$config" AuthorizedDefault none
|
||||
usb_authorization_require_secure_setting "$config" RestoreControllerDeviceState false
|
||||
}
|
||||
|
||||
usb_authorization_generate_policy() {
|
||||
local policy="$1"
|
||||
|
||||
usbguard generate-policy >"$policy"
|
||||
if [[ ! -s $policy ]] || ! grep -q '^allow ' "$policy"; then
|
||||
echo "USBGuard did not generate a usable policy; nothing was enabled." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
usb_authorization_add_user() {
|
||||
local user="$1"
|
||||
|
||||
# This user can inspect USBGuard events and approve the device named by an
|
||||
# event. Policy and daemon parameters remain unavailable through the IPC ACL.
|
||||
usbguard add-user "$user" \
|
||||
--devices=list,listen,modify \
|
||||
--policy=list \
|
||||
--exceptions=listen
|
||||
}
|
||||
|
||||
usb_authorization_trust_present_devices() {
|
||||
local line id
|
||||
local count=0
|
||||
local -a devices
|
||||
|
||||
mapfile -t devices < <(usbguard list-devices)
|
||||
|
||||
for line in "${devices[@]}"; do
|
||||
if [[ $line =~ ^([0-9]+):[[:space:]] ]]; then
|
||||
id="${BASH_REMATCH[1]}"
|
||||
usbguard allow-device --permanent "$id"
|
||||
(( ++count ))
|
||||
fi
|
||||
done
|
||||
|
||||
if (( count == 0 )); then
|
||||
echo "USBGuard did not report any connected USB devices; boot authorization was not enabled." >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
@@ -133,6 +133,7 @@ udiskie
|
||||
ufw
|
||||
ufw-docker
|
||||
unzip
|
||||
usbguard
|
||||
usage
|
||||
uwsm
|
||||
vi
|
||||
|
||||
@@ -18,4 +18,5 @@ systemctl --user enable --now \
|
||||
omarchy-sleep-lock.service \
|
||||
omarchy-migrate-notify.service \
|
||||
omarchy-fcitx5.service \
|
||||
omarchy-crash-watch.service
|
||||
omarchy-crash-watch.service \
|
||||
omarchy-usb-authorization.service
|
||||
+14
-1
@@ -6,7 +6,20 @@ Omarchy takes security extremely seriously. This is meant to be an operating sys
|
||||
2. *Firewall is enabled by default*: All incoming traffic is blocked by default except for port 53317 for [LocalSend](https://localsend.org/). Even ssh is off until you turn it on via _Setup > Security > SSHD_, which opens port 22 (rate limited against brute force) as part of the setup. We even lock down Docker access using the [ufw-docker](https://github.com/chaifeng/ufw-docker) setup to prevent that your containers are accidentally exposed to the world.
|
||||
3. *Arch always have the latest updates*: Arch, the underlying distro that Omarchy is built on, is a rolling distribution. This means that any security vulnerability that's discovered and patched in any package is quickly available for install using `omarchy-update`. You're always running the latest, most secure versions of everything that way.
|
||||
4. *Omarchy maintains its own packages and mirror*: Omarchy only relies on packages from Arch's own core/extra/multilib repositories and its own Omarchy Package Repository by default. You can install software directly from AUR, but the base install doesn't — only a few optional installs, like the third-party browsers, pull from the AUR.
|
||||
5. *Cloudflare protects us from DDoS*: All the Omarchy distribution infrastructure — the ISOs, the Omarchy packages, the Arch mirror — is protected behind Cloudflare's formidable DDoS shield and hosted on their CDN. This provides superb availability.
|
||||
5. *New USB devices require approval*: Omarchy trusts the USB devices present during installation, then blocks newly connected accessories until you approve them. This keeps an unknown accessory from reaching a matching kernel driver merely because it claims a supported device identity.
|
||||
6. *Cloudflare protects us from DDoS*: All the Omarchy distribution infrastructure — the ISOs, the Omarchy packages, the Arch mirror — is protected behind Cloudflare's formidable DDoS shield and hosted on their CDN. This provides superb availability.
|
||||
|
||||
## USB Device Authorization
|
||||
|
||||
USB device authorization is enabled by default. Omarchy uses USBGuard to trust the devices connected during installation and block USB devices it has not seen before. When a device is blocked, click the persistent notification and choose whether to allow it once, always allow it, or keep it blocked.
|
||||
|
||||
You can turn the protection off from _Remove > Security > USB Device Authorization_. _Setup > Security > USB Device Authorization_ turns it back on with the existing trusted-device policy.
|
||||
|
||||
Only approve hardware you recognize. A malicious USB device can forge the manufacturer, model, serial number, and device class shown during review. Permanent approval is a convenience for accessories you control, not proof of the device's identity.
|
||||
|
||||
The policy protects devices connected after USBGuard starts during boot. It does not replace operating-system and driver security updates, and USB devices needed before the service starts, such as a keyboard used to unlock an encrypted disk, remain governed by the kernel's boot-time policy.
|
||||
|
||||
For machines whose disk unlock and recovery never depend on USB input, _Setup > Security > USB at Boot_ can extend the block to the beginning of kernel boot. Omarchy adds every currently connected device to USBGuard's trusted policy, sets `usbcore.authorized_default=0`, and updates current and Limine snapshot boot entries. The trusted policy applies only once USBGuard starts: even a trusted USB keyboard remains unavailable at an earlier encrypted-disk password prompt. _Remove > Security > USB at Boot_ restores the normal early-boot behavior while keeping USBGuard active after startup.
|
||||
|
||||
## Changing your passwords
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
echo "Enable approval for newly connected USB devices"
|
||||
|
||||
omarchy-setup-security-usb-authorization --yes
|
||||
@@ -324,6 +324,13 @@ assert(
|
||||
defaultById['setup.security.passwordless-sudo'].action.includes('omarchy-sudo-passwordless'),
|
||||
'menu places Passwordless Sudo under Setup > Security'
|
||||
)
|
||||
assert(
|
||||
defaultById['setup.security.usb-authorization'].action.includes('omarchy-setup-security-usb-authorization')
|
||||
&& defaultById['remove.security.usb-authorization'].action.includes('omarchy-remove-security-usb-authorization')
|
||||
&& defaultById['setup.security.usb-authorization-boot'].action.includes('omarchy-setup-security-usb-authorization --boot')
|
||||
&& defaultById['remove.security.usb-authorization-boot'].action.includes('omarchy-remove-security-usb-authorization --boot-only'),
|
||||
'menu can enable and remove USB device authorization under Security'
|
||||
)
|
||||
assert(
|
||||
!defaultById['trigger.toggle.direct-boot'] && !defaultById['trigger.toggle.passwordless-sudo'],
|
||||
'menu removes the relocated toggles from Trigger > Toggle'
|
||||
|
||||
Executable
+405
@@ -0,0 +1,405 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
scratch=$(mktemp -d)
|
||||
trap 'rm -rf "$scratch"' EXIT
|
||||
|
||||
stub_bin="$scratch/bin"
|
||||
home="$scratch/home"
|
||||
rules="$scratch/etc/usbguard/rules.conf"
|
||||
daemon_config="$scratch/etc/usbguard/usbguard-daemon.conf"
|
||||
calls="$scratch/calls"
|
||||
|
||||
mkdir -p "$stub_bin" "$home" "${rules%/*}"
|
||||
: >"$rules"
|
||||
: >"$calls"
|
||||
|
||||
cat >"$daemon_config" <<'CONF'
|
||||
ImplicitPolicyTarget=block
|
||||
PresentDevicePolicy=apply-policy
|
||||
InsertedDevicePolicy=apply-policy
|
||||
AuthorizedDefault=none
|
||||
RestoreControllerDeviceState=false
|
||||
CONF
|
||||
|
||||
cat >"$stub_bin/omarchy-pkg-missing" <<'STUB'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
STUB
|
||||
cat >"$stub_bin/omarchy-pkg-present" <<'STUB'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
STUB
|
||||
cat >"$stub_bin/omarchy-pkg-add" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf 'pkg-add <%s>\n' "$*" >>"$CALLS"
|
||||
STUB
|
||||
cat >"$stub_bin/omarchy-pkg-drop" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf 'pkg-drop <%s>\n' "$*" >>"$CALLS"
|
||||
STUB
|
||||
cat >"$stub_bin/install" <<'STUB'
|
||||
#!/bin/bash
|
||||
if [[ $1 == -Dm600 && $2 == -o && $3 == root && $4 == -g && $5 == root ]]; then
|
||||
exec /usr/bin/install -Dm600 "$6" "$7"
|
||||
fi
|
||||
exec /usr/bin/install "$@"
|
||||
STUB
|
||||
cat >"$stub_bin/systemctl" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf 'systemctl <%s>\n' "$*" >>"$CALLS"
|
||||
if [[ $* == "is-enabled --quiet usbguard.service" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
STUB
|
||||
cat >"$stub_bin/usbguard" <<'STUB'
|
||||
#!/bin/bash
|
||||
case "$1" in
|
||||
generate-policy)
|
||||
echo 'usbguard <generate-policy>' >>"$CALLS"
|
||||
echo 'allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"'
|
||||
;;
|
||||
list-devices)
|
||||
if [[ ${2:-} == "--blocked" ]]; then
|
||||
if [[ ${BLOCKED_DEVICE_PRESENT:-1} == 1 ]]; then
|
||||
rule="${BLOCKED_DEVICE_RULE:?}"
|
||||
if [[ -n ${BLOCKED_DEVICE_RULE_FILE:-} && -f $BLOCKED_DEVICE_RULE_FILE ]]; then
|
||||
rule=$(<"$BLOCKED_DEVICE_RULE_FILE")
|
||||
fi
|
||||
printf '17: %s\n' "$rule"
|
||||
fi
|
||||
else
|
||||
echo '4: allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"'
|
||||
echo '5: allow id 0627:0001 name "QEMU USB Tablet" hash "tablet"'
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
printf 'usbguard' >>"$CALLS"
|
||||
printf ' <%s>' "$@" >>"$CALLS"
|
||||
printf '\n' >>"$CALLS"
|
||||
;;
|
||||
esac
|
||||
STUB
|
||||
cat >"$stub_bin/sudo" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf 'sudo' >>"$CALLS"
|
||||
printf ' <%s>' "$@" >>"$CALLS"
|
||||
printf '\n' >>"$CALLS"
|
||||
if [[ $1 == awk && ${!#} == /etc/usbguard/usbguard-daemon.conf ]]; then
|
||||
args=("$@")
|
||||
args[-1]="$TEST_DAEMON_CONFIG"
|
||||
exec "${args[@]}"
|
||||
elif [[ $1 == test && $2 == -s && $3 == /etc/usbguard/rules.conf ]]; then
|
||||
exec /usr/bin/test -s "$TEST_RULES"
|
||||
elif [[ $1 == install && $2 == -Dm600 && $3 == -o && $4 == root && $5 == -g && $6 == root && $8 == /etc/usbguard/rules.conf ]]; then
|
||||
exec /usr/bin/install -Dm600 "$7" "$TEST_RULES"
|
||||
elif [[ $1 == omarchy-usb-authorization-restore-default ]]; then
|
||||
echo 1 >"$TEST_SYSFS/usb1/authorized_default"
|
||||
echo 1 >"$TEST_SYSFS/1-2/authorized"
|
||||
exit 0
|
||||
elif [[ $1 == omarchy-usb-authorization-boot ]]; then
|
||||
exit 0
|
||||
fi
|
||||
exec "$@"
|
||||
STUB
|
||||
cat >"$stub_bin/gum" <<'STUB'
|
||||
#!/bin/bash
|
||||
case "$1" in
|
||||
style) exit 0 ;;
|
||||
choose)
|
||||
if [[ -n ${GUM_REASSIGN_RULE:-} ]]; then
|
||||
printf '%s\n' "$GUM_REASSIGN_RULE" >"$BLOCKED_DEVICE_RULE_FILE"
|
||||
fi
|
||||
printf '%s\n' "${GUM_CHOICE:-Keep blocked}"
|
||||
;;
|
||||
confirm) exit 0 ;;
|
||||
esac
|
||||
STUB
|
||||
cat >"$stub_bin/omarchy-notification-send" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf 'notification' >>"$CALLS"
|
||||
printf ' <%s>' "$@" >>"$CALLS"
|
||||
printf '\n' >>"$CALLS"
|
||||
STUB
|
||||
chmod +x "$stub_bin"/*
|
||||
|
||||
export HOME="$home"
|
||||
export USER=tester
|
||||
export OMARCHY_PATH="$ROOT"
|
||||
export CALLS="$calls"
|
||||
export PATH="$stub_bin:$ROOT/bin:$PATH"
|
||||
export TEST_DAEMON_CONFIG="$daemon_config"
|
||||
export TEST_RULES="$rules"
|
||||
|
||||
if "$ROOT/bin/omarchy-usb-authorization-restore-default" >/dev/null 2>&1; then
|
||||
fail "the fixed-path USB authorization restore helper requires root"
|
||||
fi
|
||||
if "$ROOT/bin/omarchy-usb-authorization-boot" enable >/dev/null 2>&1; then
|
||||
fail "the fixed-path boot authorization helper requires root"
|
||||
fi
|
||||
! grep -q 'OMARCHY_USB_AUTHORIZATION_.*ROOT\|OMARCHY_USB_AUTHORIZATION_.*FILE\|OMARCHY_USB_AUTHORIZATION_.*CONFIG' \
|
||||
"$ROOT/bin/omarchy-setup-security-usb-authorization" \
|
||||
"$ROOT/bin/omarchy-remove-security-usb-authorization" \
|
||||
"$ROOT/bin/omarchy-usb-authorization-boot" ||
|
||||
fail "user-facing USB authorization commands do not elevate caller-selected paths"
|
||||
pass "USB authorization keeps privileged paths fixed"
|
||||
|
||||
"$ROOT/bin/omarchy-setup-security-usb-authorization" --yes >"$scratch/setup-output"
|
||||
|
||||
grep -qx 'allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"' "$rules" ||
|
||||
fail "USB authorization setup trusts devices present during enrollment"
|
||||
[[ $(stat -c %a "$rules") == 600 ]] || fail "USB authorization policy is root-private"
|
||||
grep -Fqx 'usbguard <add-user> <tester> <--devices=list,listen,modify> <--policy=list> <--exceptions=listen>' "$calls" ||
|
||||
fail "USB authorization grants only the IPC access needed by the approval flow"
|
||||
grep -Fqx 'systemctl <restart usbguard.service>' "$calls" ||
|
||||
fail "USB authorization reloads its user ACL before the watcher starts"
|
||||
[[ -L $home/.config/systemd/user/omarchy-usb-authorization.service ]] ||
|
||||
fail "USB authorization installs the graphical-session watcher"
|
||||
grep -Fqx 'pkg-add <usbguard>' "$calls" || fail "USB authorization ensures USBGuard is installed"
|
||||
pass "USB authorization enrolls present devices before enabling default-deny"
|
||||
|
||||
generate_count=$(grep -c '^usbguard <generate-policy>$' "$calls")
|
||||
"$ROOT/bin/omarchy-setup-security-usb-authorization" --yes >"$scratch/setup-existing-output"
|
||||
generate_count_after=$(grep -c '^usbguard <generate-policy>$' "$calls")
|
||||
(( generate_count_after == generate_count )) || fail "re-enabling preserves the existing trusted-device policy"
|
||||
pass "USB authorization setup preserves an existing policy"
|
||||
|
||||
: >"$calls"
|
||||
"$ROOT/bin/omarchy-setup-security-usb-authorization" --boot --yes >"$scratch/setup-boot-output"
|
||||
grep -Fqx 'usbguard <allow-device> <--permanent> <4>' "$calls" ||
|
||||
fail "boot authorization trusts the connected root controller"
|
||||
grep -Fqx 'usbguard <allow-device> <--permanent> <5>' "$calls" ||
|
||||
fail "boot authorization trusts the connected USB devices"
|
||||
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <enable>' "$calls" ||
|
||||
fail "boot authorization uses the fixed-path boot-image helper"
|
||||
grep -Fq 'takes effect after reboot' "$scratch/setup-boot-output" ||
|
||||
fail "boot authorization explains when the change becomes active"
|
||||
pass "USB authorization can snapshot connected devices for early-boot denial"
|
||||
|
||||
boot_conf="$scratch/limine.conf"
|
||||
machine_id=$(</etc/machine-id)
|
||||
touch "$scratch/vmlinuz-linux"
|
||||
boot_hash=$(b2sum "$scratch/vmlinuz-linux")
|
||||
boot_hash=${boot_hash%% *}
|
||||
printf 'quiet splash\0' >"$scratch/embedded-cmdline"
|
||||
historical_uki="$scratch/embedded.efi_sha256_fixture"
|
||||
objcopy -I binary -O pei-x86-64 -B i386:x86-64 "$scratch/embedded-cmdline" "$historical_uki"
|
||||
objcopy --rename-section .data=.cmdline,alloc,load,readonly,data,contents "$historical_uki"
|
||||
embedded_hash=$(b2sum "$historical_uki")
|
||||
embedded_hash=${embedded_hash%% *}
|
||||
cat >"$boot_conf" <<CONF
|
||||
/+Omarchy
|
||||
comment: machine-id=$machine_id
|
||||
//linux
|
||||
comment: kernel-id=linux
|
||||
path: boot():/vmlinuz-linux#$boot_hash
|
||||
cmdline: quiet splash
|
||||
//linux-embedded
|
||||
comment: kernel-id=linux-embedded
|
||||
path: boot():/embedded.efi_sha256_fixture#$embedded_hash
|
||||
cmdline: quiet splash
|
||||
///Snapshots
|
||||
////linux
|
||||
comment: kernel-id=linux
|
||||
path: boot():/vmlinuz-linux#$boot_hash
|
||||
cmdline: rootflags=subvol=/snapshot quiet splash
|
||||
/Other Linux
|
||||
comment: machine-id=ffffffffffffffffffffffffffffffff
|
||||
//linux
|
||||
comment: kernel-id=linux
|
||||
path: boot():/other-linux
|
||||
cmdline: quiet splash
|
||||
CONF
|
||||
|
||||
source "$ROOT/bin/omarchy-usb-authorization-boot"
|
||||
limine-mkinitcpio() { return 0; }
|
||||
limine-enroll-config() { return 0; }
|
||||
|
||||
if usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
|
||||
fail "boot authorization rejects Limine's false-success status when entries were not rebuilt"
|
||||
fi
|
||||
usb_authorization_rewrite_boot_cmdlines enabled "$boot_conf" "$machine_id"
|
||||
usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" disabled ||
|
||||
fail "a non-Secure-Boot snapshot uses its updated external command line"
|
||||
if usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
|
||||
fail "an external allow parameter cannot hide a stale embedded UKI command line"
|
||||
fi
|
||||
printf 'quiet splash usbcore.authorized_default=0\0' >"$scratch/embedded-cmdline"
|
||||
objcopy -I binary -O pei-x86-64 -B i386:x86-64 "$scratch/embedded-cmdline" "$historical_uki"
|
||||
objcopy --rename-section .data=.cmdline,alloc,load,readonly,data,contents "$historical_uki"
|
||||
updated_embedded_hash=$(b2sum "$historical_uki")
|
||||
updated_embedded_hash=${updated_embedded_hash%% *}
|
||||
sed -i "s/embedded.efi_sha256_fixture#$embedded_hash/embedded.efi_sha256_fixture#$updated_embedded_hash/" "$boot_conf"
|
||||
usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled ||
|
||||
fail "boot authorization updates current and snapshot entries for this machine"
|
||||
[[ $(grep -c 'usbcore.authorized_default=0' "$boot_conf") == 3 ]] ||
|
||||
fail "boot authorization leaves another operating system's kernel entries unchanged"
|
||||
printf 'stale image' >"$scratch/vmlinuz-linux"
|
||||
if usb_authorization_rebuild_and_verify enabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
|
||||
fail "boot authorization rejects a stale Limine verification hash"
|
||||
fi
|
||||
: >"$scratch/vmlinuz-linux"
|
||||
if usb_authorization_rebuild_and_verify disabled "$boot_conf" "$scratch" "$machine_id" enabled 2>/dev/null; then
|
||||
fail "boot authorization detects a stale deny-by-default boot entry during removal"
|
||||
fi
|
||||
usb_authorization_rewrite_boot_cmdlines disabled "$boot_conf" "$machine_id"
|
||||
printf 'quiet splash\0' >"$scratch/embedded-cmdline"
|
||||
objcopy -I binary -O pei-x86-64 -B i386:x86-64 "$scratch/embedded-cmdline" "$historical_uki"
|
||||
objcopy --rename-section .data=.cmdline,alloc,load,readonly,data,contents "$historical_uki"
|
||||
disabled_embedded_hash=$(b2sum "$historical_uki")
|
||||
disabled_embedded_hash=${disabled_embedded_hash%% *}
|
||||
sed -i "s/embedded.efi_sha256_fixture#$updated_embedded_hash/embedded.efi_sha256_fixture#$disabled_embedded_hash/" "$boot_conf"
|
||||
usb_authorization_rebuild_and_verify disabled "$boot_conf" "$scratch" "$machine_id" enabled ||
|
||||
fail "boot authorization removes the parameter from current and snapshot entries"
|
||||
|
||||
snapshot_config="$scratch/limine-defaults"
|
||||
printf 'TARGET_OS_NAME="Omarchy"\n' >"$snapshot_config"
|
||||
usb_authorization_enable_snapshot_setting "$snapshot_config"
|
||||
grep -Fqx 'SNAPSHOT_KERNEL_PARAMETERS+=usbcore.authorized_default=0' "$snapshot_config" ||
|
||||
fail "future Limine snapshots inherit boot-time USB authorization"
|
||||
usb_authorization_enable_snapshot_setting "$snapshot_config"
|
||||
[[ $(grep -Fxc '# Omarchy USB authorization begin' "$snapshot_config") == 1 ]] ||
|
||||
fail "the snapshot setting remains idempotent"
|
||||
usb_authorization_disable_snapshot_setting "$snapshot_config"
|
||||
! grep -Fq 'usbcore.authorized_default=0' "$snapshot_config" ||
|
||||
fail "removing boot authorization restores future snapshot defaults"
|
||||
pass "boot authorization verifies generated entries despite Limine false-success exits"
|
||||
|
||||
: >"$calls"
|
||||
"$ROOT/bin/omarchy-remove-security-usb-authorization" --boot-only --yes >"$scratch/remove-boot-output"
|
||||
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <disable>' "$calls" ||
|
||||
fail "boot-only removal rebuilds the permissive boot policy"
|
||||
! grep -Fq 'systemctl <--user disable --now omarchy-usb-authorization.service>' "$calls" ||
|
||||
fail "boot-only removal leaves the userspace approval watcher enabled"
|
||||
grep -Fq 'takes effect after reboot' "$scratch/remove-boot-output" ||
|
||||
fail "boot-only removal explains when the change becomes active"
|
||||
pass "early-boot denial can be removed without disabling USBGuard"
|
||||
|
||||
grep -qx 'usbguard' "$ROOT/install/omarchy-base.packages" || fail "USBGuard is installed by default"
|
||||
grep -Fq 'config/usb-authorization.sh' "$ROOT/install/config/all.sh" ||
|
||||
fail "fresh installs configure the USB authorization policy"
|
||||
grep -Fq 'omarchy-usb-authorization.service' "$ROOT/install/user/first-run/enable-user-units.sh" ||
|
||||
fail "fresh installs enable the USB approval watcher"
|
||||
grep -Fq 'usb_authorization_add_user "$username"' "$ROOT/bin/omarchy-provision-owner" ||
|
||||
fail "deferred provisioning grants the new owner approval access"
|
||||
grep -Fq 'systemctl try-restart usbguard.service' "$ROOT/bin/omarchy-provision-owner" ||
|
||||
fail "deferred provisioning reloads the new owner ACL"
|
||||
grep -Fqx 'omarchy-setup-security-usb-authorization --yes' "$ROOT/migrations/1789433473.sh" ||
|
||||
fail "existing installs enable USB authorization during migration"
|
||||
|
||||
: >"$rules"
|
||||
: >"$calls"
|
||||
export OMARCHY_INSTALL="$ROOT/install"
|
||||
export OMARCHY_INSTALL_USER=tester
|
||||
export OMARCHY_USB_AUTHORIZATION_RULES_FILE="$rules"
|
||||
export OMARCHY_USB_AUTHORIZATION_DAEMON_CONFIG="$daemon_config"
|
||||
bash -euo pipefail -c 'source "$OMARCHY_INSTALL/config/usb-authorization.sh"' >"$scratch/install-output"
|
||||
grep -qx 'allow id 1d6b:0002 name "Linux Foundation root hub" hash "root"' "$rules" ||
|
||||
fail "fresh installation enrolls devices present during installation"
|
||||
grep -Fqx 'usbguard <add-user> <tester> <--devices=list,listen,modify> <--policy=list> <--exceptions=listen>' "$calls" ||
|
||||
fail "fresh installation grants the owner narrowly scoped approval access"
|
||||
grep -Fqx 'systemctl <enable usbguard.service>' "$calls" ||
|
||||
fail "fresh installation enables USBGuard for the first boot"
|
||||
pass "USB authorization is the default for fresh and existing installations"
|
||||
|
||||
malicious_rule='block id 07a6:8513 name "$(touch '"$scratch"'/injected)" hash "attacker" with-interface 02:06:00'
|
||||
export BLOCKED_DEVICE_RULE="$malicious_rule"
|
||||
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
|
||||
USBGUARD_DEVICE_ID=17 \
|
||||
USBGUARD_DEVICE_EVENT=Insert \
|
||||
USBGUARD_DEVICE_TARGET=block \
|
||||
USBGUARD_DEVICE_RULE="$malicious_rule" \
|
||||
"$ROOT/bin/omarchy-usb-authorization-event"
|
||||
|
||||
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
|
||||
[[ -n $request ]] || fail "a blocked USB device creates a review request"
|
||||
[[ $(stat -c %a "$request") == 600 ]] || fail "device-controlled request data stays private"
|
||||
[[ $(jq -r .id "$request") == 17 ]] || fail "the request records the USBGuard device id"
|
||||
[[ $(jq -r .rule "$request") == "$malicious_rule" ]] || fail "the request preserves the identity snapshot"
|
||||
! grep -Fq "$malicious_rule" "$calls" || fail "device text never enters the notification's launch command"
|
||||
[[ ! -e $scratch/injected ]] || fail "device text cannot execute during notification"
|
||||
token=$(basename "${request%.json}")
|
||||
grep -Eq "notification .*<omarchy-usb-authorization-review> <$token>$" "$calls" ||
|
||||
fail "the notification action carries only an opaque request token"
|
||||
pass "blocked device metadata remains data across the desktop notification boundary"
|
||||
|
||||
GUM_CHOICE='Allow once' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-once-output"
|
||||
grep -Fqx "usbguard <allow-device> <$malicious_rule>" "$calls" || fail "review can allow the exact device once"
|
||||
[[ ! -e $request ]] || fail "a completed review consumes its request"
|
||||
pass "USB review allows a still-matching device once"
|
||||
|
||||
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
|
||||
USBGUARD_DEVICE_ID=17 \
|
||||
USBGUARD_DEVICE_EVENT=Insert \
|
||||
USBGUARD_DEVICE_TARGET=block \
|
||||
USBGUARD_DEVICE_RULE="$malicious_rule" \
|
||||
"$ROOT/bin/omarchy-usb-authorization-event"
|
||||
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
|
||||
token=$(basename "${request%.json}")
|
||||
GUM_CHOICE='Always allow this device' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-always-output"
|
||||
grep -Fqx "usbguard <allow-device> <--permanent> <$malicious_rule>" "$calls" || fail "review can persist an exact-device rule"
|
||||
pass "USB review supports explicit persistent trust"
|
||||
|
||||
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
|
||||
USBGUARD_DEVICE_ID=17 \
|
||||
USBGUARD_DEVICE_EVENT=Insert \
|
||||
USBGUARD_DEVICE_TARGET=block \
|
||||
USBGUARD_DEVICE_RULE="$malicious_rule" \
|
||||
"$ROOT/bin/omarchy-usb-authorization-event"
|
||||
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
|
||||
token=$(basename "${request%.json}")
|
||||
device_rule_file="$scratch/blocked-device-rule"
|
||||
printf '%s\n' "$malicious_rule" >"$device_rule_file"
|
||||
replacement_rule='block id 1d50:60c7 name "Replacement gadget" hash "replacement"'
|
||||
allow_count=$(grep -c '^usbguard <allow-device>' "$calls")
|
||||
if BLOCKED_DEVICE_RULE_FILE="$device_rule_file" \
|
||||
GUM_REASSIGN_RULE="$replacement_rule" \
|
||||
GUM_CHOICE='Always allow this device' \
|
||||
"$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-reassigned-output" 2>&1; then
|
||||
fail "review rejects a USBGuard ID reassigned while the approval dialog is open"
|
||||
fi
|
||||
[[ $(grep -c '^usbguard <allow-device>' "$calls") == "$allow_count" ]] ||
|
||||
fail "an ID reassigned during review cannot authorize a different device"
|
||||
[[ ! -e $request ]] || fail "a reassigned-ID review request is discarded"
|
||||
pass "USB review revalidates identity after the approval dialog"
|
||||
|
||||
USBGUARD_IPC_SIGNAL=Device.PresenceChanged \
|
||||
USBGUARD_DEVICE_ID=17 \
|
||||
USBGUARD_DEVICE_EVENT=Insert \
|
||||
USBGUARD_DEVICE_TARGET=block \
|
||||
USBGUARD_DEVICE_RULE="$malicious_rule" \
|
||||
"$ROOT/bin/omarchy-usb-authorization-event"
|
||||
request=$(find "$home/.local/state/omarchy/usb-authorization/requests" -maxdepth 1 -name 'request-*.json' -print -quit)
|
||||
token=$(basename "${request%.json}")
|
||||
allow_count=$(grep -c '^usbguard <allow-device>' "$calls")
|
||||
if BLOCKED_DEVICE_PRESENT=0 "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-stale-output" 2>&1; then
|
||||
fail "review rejects a request after its device disappears"
|
||||
fi
|
||||
[[ $(grep -c '^usbguard <allow-device>' "$calls") == "$allow_count" ]] ||
|
||||
fail "a reused USBGuard id cannot authorize a different device"
|
||||
[[ ! -e $request ]] || fail "a stale review request is discarded"
|
||||
pass "USB review binds approval to the device snapshot"
|
||||
|
||||
sysfs="$scratch/sys/bus/usb/devices"
|
||||
mkdir -p "$sysfs/usb1" "$sysfs/1-2"
|
||||
echo 0 >"$sysfs/usb1/authorized_default"
|
||||
echo 0 >"$sysfs/1-2/authorized"
|
||||
export TEST_SYSFS="$sysfs"
|
||||
|
||||
"$ROOT/bin/omarchy-remove-security-usb-authorization" --yes >"$scratch/remove-output"
|
||||
|
||||
[[ $(<"$sysfs/usb1/authorized_default") == 1 ]] || fail "removal restores root-hub default authorization"
|
||||
[[ $(<"$sysfs/1-2/authorized") == 1 ]] || fail "removal reauthorizes devices left connected"
|
||||
grep -Fqx 'systemctl <disable --now usbguard.service>' "$calls" || fail "removal stops a service Omarchy enabled"
|
||||
grep -Fqx 'usbguard <remove-user> <tester>' "$calls" || fail "removal drops the user's USBGuard IPC access"
|
||||
grep -Fqx 'sudo <omarchy-usb-authorization-restore-default>' "$calls" ||
|
||||
fail "removal restores USB authorization only through the fixed-path root helper"
|
||||
grep -Fqx 'sudo <omarchy-usb-authorization-boot> <disable>' "$calls" ||
|
||||
fail "removing USB authorization also removes early-boot denial"
|
||||
! grep -Fqx 'pkg-drop <usbguard>' "$calls" || fail "disabling keeps the default USBGuard package installed"
|
||||
[[ ! -e $home/.config/systemd/user/omarchy-usb-authorization.service ]] ||
|
||||
fail "removal deletes the graphical-session watcher"
|
||||
pass "USB authorization removal restores the original default-allow behavior"
|
||||
Reference in new issue
Block a user