From 7309746ac842a30774be8da850ce358306bc76df Mon Sep 17 00:00:00 2001 From: acrogenesis Date: Tue, 15 Sep 2026 13:05:25 -0600 Subject: [PATCH] Fix USB approval alerts after policy changes and reconnects --- bin/omarchy-usb-authorization-event | 44 ++++++++++++++-- bin/omarchy-usb-authorization-watch | 15 +----- test/shell.d/usb-authorization-test.sh | 72 +++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 24 deletions(-) diff --git a/bin/omarchy-usb-authorization-event b/bin/omarchy-usb-authorization-event index 53182715..31031127 100755 --- a/bin/omarchy-usb-authorization-event +++ b/bin/omarchy-usb-authorization-event @@ -5,9 +5,30 @@ set -euo pipefail -[[ ${USBGUARD_IPC_SIGNAL:-} == "Device.PresenceChanged" ]] || exit 0 -[[ ${USBGUARD_DEVICE_EVENT:-} == "Insert" ]] || exit 0 -[[ ${USBGUARD_DEVICE_TARGET:-} == "block" ]] || exit 0 +case "${USBGUARD_IPC_SIGNAL:-}" in +IPC.Connected) + # USBGuard scans present devices before accepting IPC connections. Repeat + # this on every reconnect: temporary approvals disappear on daemon restart. + blocked=$(usbguard list-devices --blocked) || exit 1 + while IFS= read -r line; do + if [[ $line =~ ^([0-9]+):[[:space:]](block.*)$ ]]; then + USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ + USBGUARD_DEVICE_ID="${BASH_REMATCH[1]}" \ + USBGUARD_DEVICE_TARGET_NEW=block \ + USBGUARD_DEVICE_RULE="${BASH_REMATCH[2]}" \ + "$OMARCHY_PATH/bin/omarchy-usb-authorization-event" + fi + done <<<"$blocked" + exit 0 + ;; +Device.PolicyApplied) + # PresenceChanged precedes policy evaluation, even for trusted devices. + # PolicyApplied also covers block-to-block and allow-to-block transitions. + [[ ${USBGUARD_DEVICE_TARGET_NEW:-} == "block" ]] || exit 0 + ;; +*) exit 0 ;; +esac + [[ ${USBGUARD_DEVICE_ID:-} =~ ^[0-9]+$ ]] || exit 0 [[ -n ${USBGUARD_DEVICE_RULE:-} ]] || exit 0 @@ -20,6 +41,23 @@ chmod 700 "${requests%/*}" "$requests" # 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" +# watch executes handlers concurrently. A reconnect scan and a policy event +# can refer to the same device; publish only one pending request. +exec 9>"$requests/.lock" +flock 9 +[[ ! -e $request ]] || exit 0 + +# Discard events overtaken by another policy decision or device replacement. +blocked=$(usbguard list-devices --blocked) || exit 1 +current=0 +while IFS= read -r line; do + if [[ $line == "$USBGUARD_DEVICE_ID: $USBGUARD_DEVICE_RULE" ]]; then + current=1 + break + fi +done <<<"$blocked" +(( current )) || exit 0 + temporary=$(mktemp "$requests/.request.XXXXXXXXXX") trap 'rm -f "$temporary"' EXIT diff --git a/bin/omarchy-usb-authorization-watch b/bin/omarchy-usb-authorization-watch index 8b22fdf4..302c7968 100755 --- a/bin/omarchy-usb-authorization-watch +++ b/bin/omarchy-usb-authorization-watch @@ -7,17 +7,6 @@ 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) - +# The handler scans blocked devices on IPC.Connected, including reconnects +# after a daemon restart, once the event subscription is established. exec usbguard watch --wait --exec "$event_handler" diff --git a/test/shell.d/usb-authorization-test.sh b/test/shell.d/usb-authorization-test.sh index 68c8ed56..69d5687c 100755 --- a/test/shell.d/usb-authorization-test.sh +++ b/test/shell.d/usb-authorization-test.sh @@ -307,10 +307,35 @@ 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 \ +notification_count=$(grep -c '^notification' "$calls" || true) +for presence in Insert Present; do + USBGUARD_IPC_SIGNAL=Device.PresenceChanged \ + USBGUARD_DEVICE_EVENT="$presence" \ + USBGUARD_DEVICE_TARGET=block \ + USBGUARD_DEVICE_ID=17 \ + USBGUARD_DEVICE_RULE="$malicious_rule" \ + "$ROOT/bin/omarchy-usb-authorization-event" +done +USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ + USBGUARD_DEVICE_TARGET_NEW=allow \ + USBGUARD_DEVICE_ID=17 \ + USBGUARD_DEVICE_RULE="${malicious_rule/#block/allow}" \ + "$ROOT/bin/omarchy-usb-authorization-event" +[[ $(grep -c '^notification' "$calls" || true) == "$notification_count" ]] || + fail "a trusted device's transient blocked presence must not prompt" +BLOCKED_DEVICE_PRESENT=0 \ + USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ + USBGUARD_DEVICE_TARGET_NEW=block \ + USBGUARD_DEVICE_ID=17 \ + USBGUARD_DEVICE_RULE="$malicious_rule" \ + "$ROOT/bin/omarchy-usb-authorization-event" +[[ $(grep -c '^notification' "$calls" || true) == "$notification_count" ]] || + fail "a stale blocked event must not prompt after authorization or removal" +pass "USB alerts use the final policy and discard stale events" +USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ USBGUARD_DEVICE_ID=17 \ USBGUARD_DEVICE_EVENT=Insert \ - USBGUARD_DEVICE_TARGET=block \ + USBGUARD_DEVICE_TARGET_NEW=block \ USBGUARD_DEVICE_RULE="$malicious_rule" \ "$ROOT/bin/omarchy-usb-authorization-event" @@ -326,15 +351,46 @@ grep -Eq "notification .* <$token>$" "$calls" fail "the notification action carries only an opaque request token" pass "blocked device metadata remains data across the desktop notification boundary" +notification_count=$(grep -c '^notification' "$calls") +USBGUARD_IPC_SIGNAL=IPC.Connected "$ROOT/bin/omarchy-usb-authorization-event" & +scan_pid=$! +USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ + USBGUARD_DEVICE_ID=17 \ + USBGUARD_DEVICE_TARGET_NEW=block \ + USBGUARD_DEVICE_RULE="$malicious_rule" \ + "$ROOT/bin/omarchy-usb-authorization-event" +wait "$scan_pid" +[[ $(grep -c '^notification' "$calls") == "$notification_count" ]] || + fail "overlapping scan and policy events must not duplicate pending prompts" +pass "USB reconnect scans and policy events deduplicate pending requests" + GUM_CHOICE='Allow once' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >"$scratch/review-once-output" grep -Fqx "usbguard <$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 \ +# A daemon restart discards the temporary approval. The IPC reconnect must +# recover the blocked device even when its startup policy events were missed. +notification_count=$(grep -c '^notification' "$calls") +USBGUARD_IPC_SIGNAL=IPC.Connected "$ROOT/bin/omarchy-usb-authorization-event" +[[ -f $request ]] || fail "reconnection recreates a consumed Allow once request" +[[ $(grep -c '^notification' "$calls") == $((notification_count + 1)) ]] || + fail "reconnection prompts again for a temporary approval lost on restart" +GUM_CHOICE='Keep blocked' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >/dev/null +USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ + USBGUARD_DEVICE_ID=17 \ + USBGUARD_DEVICE_TARGET_OLD=allow \ + USBGUARD_DEVICE_TARGET_NEW=block \ + USBGUARD_DEVICE_RULE="$malicious_rule" \ + "$ROOT/bin/omarchy-usb-authorization-event" +[[ -f $request ]] || fail "a later allow-to-block policy transition creates a request" +GUM_CHOICE='Keep blocked' "$ROOT/bin/omarchy-usb-authorization-review" "$token" >/dev/null +pass "USB approval recovers after IPC reconnects and policy transitions" + +USBGUARD_IPC_SIGNAL=Device.PolicyApplied \ USBGUARD_DEVICE_ID=17 \ USBGUARD_DEVICE_EVENT=Insert \ - USBGUARD_DEVICE_TARGET=block \ + USBGUARD_DEVICE_TARGET_NEW=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) @@ -343,10 +399,10 @@ GUM_CHOICE='Always allow this device' "$ROOT/bin/omarchy-usb-authorization-revie grep -Fqx "usbguard <--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_IPC_SIGNAL=Device.PolicyApplied \ USBGUARD_DEVICE_ID=17 \ USBGUARD_DEVICE_EVENT=Insert \ - USBGUARD_DEVICE_TARGET=block \ + USBGUARD_DEVICE_TARGET_NEW=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) @@ -366,10 +422,10 @@ fi [[ ! -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_IPC_SIGNAL=Device.PolicyApplied \ USBGUARD_DEVICE_ID=17 \ USBGUARD_DEVICE_EVENT=Insert \ - USBGUARD_DEVICE_TARGET=block \ + USBGUARD_DEVICE_TARGET_NEW=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)