From a516ba24f1635ac54b0ada4957fab58616d1badd Mon Sep 17 00:00:00 2001 From: blak0p Date: Thu, 17 Sep 2026 17:47:39 +0200 Subject: [PATCH 1/3] fix(udev): grant uaccess to X6 hidraw nodes --- docs/linux-usb-prerequisites.md | 14 +-- odd/tasks/udev-hidraw-uaccess.md | 54 +++++++++++ ....rules => 60-attack-shark-x6-hidraw.rules} | 0 packaging/udev/99-attack-shark-x6.rules | 5 - packaging/udev/udev_policy_tdd_test.go | 91 +++++++++++++++++++ 5 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 odd/tasks/udev-hidraw-uaccess.md rename packaging/udev/{99-attack-shark-x6-hidraw.rules => 60-attack-shark-x6-hidraw.rules} (100%) delete mode 100644 packaging/udev/99-attack-shark-x6.rules create mode 100644 packaging/udev/udev_policy_tdd_test.go diff --git a/docs/linux-usb-prerequisites.md b/docs/linux-usb-prerequisites.md index 4196f14..a23ede7 100644 --- a/docs/linux-usb-prerequisites.md +++ b/docs/linux-usb-prerequisites.md @@ -11,7 +11,7 @@ the device world-writable. 1. From the repository root, copy the shipped rule and reload udev: ```sh - sudo install -Dm0644 packaging/udev/99-attack-shark-x6.rules /etc/udev/rules.d/99-attack-shark-x6.rules + sudo install -Dm0644 packaging/udev/60-attack-shark-x6-hidraw.rules /etc/udev/rules.d/60-attack-shark-x6-hidraw.rules sudo udevadm control --reload-rules sudo udevadm trigger ``` @@ -41,14 +41,14 @@ seat user. On systems without logind/uaccess support, use a static group instead ```sh sudo groupadd --system attack-shark-x6 sudo usermod -aG attack-shark-x6 "$USER" -sudo install -Dm0644 packaging/udev/99-attack-shark-x6.rules /etc/udev/rules.d/99-attack-shark-x6.rules +sudo install -Dm0644 packaging/udev/60-attack-shark-x6-hidraw.rules /etc/udev/rules.d/60-attack-shark-x6-hidraw.rules ``` Then replace the installed rule's final action with this group policy, reload udev, and replug the dongle: ```udev -SUBSYSTEM=="usb", ATTRS{idVendor}=="1d57", ATTRS{idProduct}=="fa60", GROUP="attack-shark-x6", MODE="0660" +SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1d57", ATTRS{idProduct}=="fa60", GROUP="attack-shark-x6", MODE="0660" ``` ```sh @@ -66,7 +66,7 @@ The application maps `os.ErrPermission` to the UI error code `permission_denied`. This means Linux denied access to the validated dongle; it does not mean the app should be run as root. Check, in order: -- The rule is installed at `/etc/udev/rules.d/99-attack-shark-x6.rules`. +- The rule is installed at `/etc/udev/rules.d/60-attack-shark-x6-hidraw.rules`. - Rules were reloaded, the trigger command ran, and the dongle was replugged. - For group policy, the user belongs to `attack-shark-x6` and has logged in again. - The dongle identity still appears as `1d57:fa60`. @@ -79,11 +79,11 @@ Confirm the USB identity: lsusb -d 1d57:fa60 ``` -Inspect udev attributes after locating the matching device node (replace the -placeholder with the bus/device path reported by your system): +Inspect udev attributes after locating an X6 hidraw node (replace the +placeholder with the hidraw node reported by your system): ```sh -udevadm info -a -n /dev/bus/usb/BBB/DDD +udevadm info -a -n /dev/hidrawN ``` The adapter reads status or acknowledgements from the vendor hidraw node only diff --git a/odd/tasks/udev-hidraw-uaccess.md b/odd/tasks/udev-hidraw-uaccess.md new file mode 100644 index 0000000..41efe80 --- /dev/null +++ b/odd/tasks/udev-hidraw-uaccess.md @@ -0,0 +1,54 @@ +# Udev Hidraw Uaccess Fix + +Repository-relative file locator: `odd/tasks/udev-hidraw-uaccess.md` + +## Objective + +Correct the packaged X6 Linux access policy so systemd processes `uaccess` before its late-seat rule and grants access to the hidraw nodes that the desktop application actually opens. + +## Problem and evidence + +The packaged file is named `99-attack-shark-x6.rules` and matches `SUBSYSTEM=="usb"`. On this host, `/usr/lib/udev/rules.d/73-seat-late.rules:16` queues its uaccess helper only when the tag already exists. Collective lexical evaluation therefore processes `73-...` before `99-...`, so the late tag cannot satisfy that earlier condition. The application uses `/dev/hidraw*`, not the USB device node. + +The issue #77 reporter observed the ordering failure on CachyOS with Hyprland. Read-only host inspection confirmed an X6 USB device and four X6 hidraw nodes. The host's local policy uses separate USB and hidraw rules, but it is customized with `OWNER="alejandro"`; it is not a suitable shipped policy or runtime proof. + +## Scope + +- Remove the superseded USB-only `99-attack-shark-x6.rules` policy and rename the existing hidraw-specific `99-attack-shark-x6-hidraw.rules` policy to `60-attack-shark-x6-hidraw.rules`, preserving its X6 parent matching, `TAG+="uaccess"`, and `MODE="0660"` contract. +- Update installation, group-policy alternative, recovery, and troubleshooting documentation to use the new path and accurately state the hidraw target. +- Run a maintainer-authorized, reversible live end-to-end acceptance before repository policy edits; prove the candidate grants the active user ACL access to the X6 hidraw nodes and permits non-mutating desktop reads. +- Post one concise clarification to issue #77: the `99 → 60` ordering diagnosis is valid, and the delivered policy must also match hidraw. + +## Constraints and non-goals + +- Preserve least privilege: do not use `0666`, root execution, broad USB access, or user-specific `OWNER=` policy. +- Do not reload rules, trigger events, replug hardware, modify `/etc` or `/run` udev rules, or run a live udev simulation **unless the maintainer explicitly authorizes the reversible UDEV-2 acceptance procedure**. +- Do not change HID transport, device discovery, Wails bindings, or remapping behavior. +- The live acceptance procedure must use a volatile `/run` candidate and exact rollback. It must temporarily neutralize the host's existing X6-specific `OWNER=` rules, reload udev, require a physical replug, verify hidraw ACLs and non-mutating desktop reads, then restore the original rules and access state even on failure. +- A repository rule contract can be validated statically; live ACL confirmation is separate maintainer-operated evidence and must not be claimed until observed. + +## Delivery strategy + +- Forecast: approximately 80–140 authored changed lines, including documentation and static contract checks. +- Strategy: `ask-on-risk`; the forecast is below the 400-line delivery heuristic. +- Effective TDD mode: enabled by explicit maintainer instruction. Runner: `go test ./packaging/udev -run '^TestPackagedUdevPolicyContract$' -count=1`. The new adjacent test `packaging/udev/udev_policy_tdd_test.go` must produce observed RED evidence before production policy/documentation edits, then observed GREEN evidence afterward. + +## Tasks + +- [x] **UDEV-1 — Establish an isolated feature workspace from `origin/main`.** Created branch `fix/udev-hidraw-uaccess` at `e4ea092d8bc71f56b852f4fada310bc82404a24e`; worktree is clean. Evidence: `git worktree add -b fix/udev-hidraw-uaccess … origin/main`. Commit: pending explicit user authorization. +- [x] **UDEV-2 — Run the reversible live end-to-end acceptance.** PASS. The volatile `60-attack-shark-x6-hidraw.rules` candidate produced `uaccess` tags and `user:alejandro:rw-` ACLs on all four X6 hidraw nodes after physical replug. The desktop Device view read interface `dongle` and battery `100%` without applying any device change. Terminal closure interrupted automatic cleanup, but exact manual restoration was then verified read-only: both root-owned baseline rules exist, the candidate and backup are absent, and all four hidraw nodes are `alejandro:root` mode `0660` with active-user read/write access. +- [x] **UDEV-3 — RED, GREEN, and REFACTOR the packaged policy and documentation.** RED observed because the new policy path was absent. GREEN observed after explicit deletion authorization: removed both obsolete `99-...` policies, renamed the correct hidraw policy to `60-attack-shark-x6-hidraw.rules`, and updated the documentation. Focused test passed; `gofmt` completed; `git diff --check` passed. +- [x] **UDEV-4 — Verify delivery evidence and clarify issue #77.** Static contract and diff checks passed. Posted and read back the corrective issue comment: https://github.com/blak0p/attack-shark-linux/issues/77#issuecomment-5717223060. It records both the ordering and hidraw-target requirements and the live acceptance evidence. +- [ ] **UDEV-5 — Create the work-unit commit.** Awaiting explicit user authorization to commit the verified change. Proposed Conventional Commit: `fix(udev): grant uaccess to X6 hidraw nodes`. + +## Acceptance criteria and checks + +- The installed filename is lexically earlier than `73-seat-late.rules`. +- The packaged rule matches `SUBSYSTEM=="hidraw"`, constrains the X6 VID/PID via parent attributes, has `TAG+="uaccess"`, and retains `MODE="0660"`. +- The packaged and documented filenames agree; no `99-attack-shark-x6.rules` reference remains. +- No broad USB-only rule, `OWNER=`, `0666`, root instruction, runtime udev reload/trigger, hardware communication, or generated-file change is introduced. +- Static checks and diff inspection are observed. Live ACL behavior is explicitly reported as maintainer verification, not automation evidence. + +## Current progress and next step + +TDD RED → GREEN → REFACTOR is complete. The live E2E acceptance passed, baseline recovery was verified, static checks passed, and issue #77 was corrected. The only pending action is the work-unit commit, which requires explicit user authorization. No commit has been created. diff --git a/packaging/udev/99-attack-shark-x6-hidraw.rules b/packaging/udev/60-attack-shark-x6-hidraw.rules similarity index 100% rename from packaging/udev/99-attack-shark-x6-hidraw.rules rename to packaging/udev/60-attack-shark-x6-hidraw.rules diff --git a/packaging/udev/99-attack-shark-x6.rules b/packaging/udev/99-attack-shark-x6.rules deleted file mode 100644 index 4c384db..0000000 --- a/packaging/udev/99-attack-shark-x6.rules +++ /dev/null @@ -1,5 +0,0 @@ -# Attack Shark X6 USB dongle access policy (VID:PID 1d57:fa60). -# TAG+="uaccess" grants the active local-seat user access; MODE="0660" keeps -# the device non-world-writable. Do not run the app as root or change this to 0666. -# Install in /etc/udev/rules.d/, reload udev rules, trigger, then replug the dongle. -SUBSYSTEM=="usb", ATTRS{idVendor}=="1d57", ATTRS{idProduct}=="fa60", TAG+="uaccess", MODE="0660" diff --git a/packaging/udev/udev_policy_tdd_test.go b/packaging/udev/udev_policy_tdd_test.go new file mode 100644 index 0000000..42b2b9c --- /dev/null +++ b/packaging/udev/udev_policy_tdd_test.go @@ -0,0 +1,91 @@ +package udev + +import ( + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +const ( + policyFile = "60-attack-shark-x6-hidraw.rules" + policyLine = `SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1d57", ATTRS{idProduct}=="fa60", TAG+="uaccess", MODE="0660"` +) + +func TestPackagedUdevPolicyContract(t *testing.T) { + packageDir := packageDirectory(t) + policyPath := filepath.Join(packageDir, policyFile) + activeLines := activeRuleLines(t, policyPath) + + if policyFile >= "73-seat-late.rules" { + t.Fatalf("policy filename %q must sort before 73-seat-late.rules", policyFile) + } + if len(activeLines) != 1 || activeLines[0] != policyLine { + t.Fatalf("active policy lines = %q, want exactly %q", activeLines, policyLine) + } + + policyText := readFile(t, policyPath) + for _, forbidden := range []string{"SUBSYSTEM==\"usb\"", "OWNER="} { + if strings.Contains(policyText, forbidden) { + t.Errorf("policy contains forbidden token %q", forbidden) + } + } + for _, obsoleteFile := range []string{ + "99-attack-shark-x6.rules", + "99-attack-shark-x6-hidraw.rules", + } { + if _, err := os.Stat(filepath.Join(packageDir, obsoleteFile)); !os.IsNotExist(err) { + t.Errorf("obsolete policy %q still exists (stat error: %v)", obsoleteFile, err) + } + } + + repositoryRoot := filepath.Dir(filepath.Dir(packageDir)) + documentation := readFile(t, filepath.Join(repositoryRoot, "docs", "linux-usb-prerequisites.md")) + for _, required := range []string{ + "packaging/udev/60-attack-shark-x6-hidraw.rules", + "/etc/udev/rules.d/60-attack-shark-x6-hidraw.rules", + } { + if !strings.Contains(documentation, required) { + t.Errorf("documentation does not reference %q", required) + } + } + for _, staleReference := range []string{ + "99-attack-shark-x6.rules", + "99-attack-shark-x6-hidraw.rules", + } { + if strings.Contains(documentation, staleReference) { + t.Errorf("documentation contains stale %s reference", staleReference) + } + } +} + +func packageDirectory(t *testing.T) string { + t.Helper() + _, sourceFile, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("locating test source with runtime.Caller") + } + return filepath.Dir(sourceFile) +} + +func activeRuleLines(t *testing.T, path string) []string { + t.Helper() + var active []string + for _, line := range strings.Split(readFile(t, path), "\n") { + line = strings.TrimSpace(line) + if line != "" && !strings.HasPrefix(line, "#") { + active = append(active, line) + } + } + return active +} + +func readFile(t *testing.T, path string) string { + t.Helper() + contents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + return string(contents) +} From c4ab29b76ec304a817e7d4dd6ee057857a263bc9 Mon Sep 17 00:00:00 2001 From: blak0p Date: Thu, 17 Sep 2026 17:49:36 +0200 Subject: [PATCH 2/3] docs(odd): record udev fix evidence --- odd/tasks/udev-hidraw-uaccess.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/odd/tasks/udev-hidraw-uaccess.md b/odd/tasks/udev-hidraw-uaccess.md index 41efe80..5d28304 100644 --- a/odd/tasks/udev-hidraw-uaccess.md +++ b/odd/tasks/udev-hidraw-uaccess.md @@ -39,7 +39,7 @@ The issue #77 reporter observed the ordering failure on CachyOS with Hyprland. R - [x] **UDEV-2 — Run the reversible live end-to-end acceptance.** PASS. The volatile `60-attack-shark-x6-hidraw.rules` candidate produced `uaccess` tags and `user:alejandro:rw-` ACLs on all four X6 hidraw nodes after physical replug. The desktop Device view read interface `dongle` and battery `100%` without applying any device change. Terminal closure interrupted automatic cleanup, but exact manual restoration was then verified read-only: both root-owned baseline rules exist, the candidate and backup are absent, and all four hidraw nodes are `alejandro:root` mode `0660` with active-user read/write access. - [x] **UDEV-3 — RED, GREEN, and REFACTOR the packaged policy and documentation.** RED observed because the new policy path was absent. GREEN observed after explicit deletion authorization: removed both obsolete `99-...` policies, renamed the correct hidraw policy to `60-attack-shark-x6-hidraw.rules`, and updated the documentation. Focused test passed; `gofmt` completed; `git diff --check` passed. - [x] **UDEV-4 — Verify delivery evidence and clarify issue #77.** Static contract and diff checks passed. Posted and read back the corrective issue comment: https://github.com/blak0p/attack-shark-linux/issues/77#issuecomment-5717223060. It records both the ordering and hidraw-target requirements and the live acceptance evidence. -- [ ] **UDEV-5 — Create the work-unit commit.** Awaiting explicit user authorization to commit the verified change. Proposed Conventional Commit: `fix(udev): grant uaccess to X6 hidraw nodes`. +- [x] **UDEV-5 — Create the work-unit commit.** Committed as `a516ba24f1635ac54b0ada4957fab58616d1badd` with `fix(udev): grant uaccess to X6 hidraw nodes`. Native assessment was unavailable/schema-incompatible, so independent verification ran and passed the focused policy test and committed-range diff check. ## Acceptance criteria and checks @@ -51,4 +51,4 @@ The issue #77 reporter observed the ordering failure on CachyOS with Hyprland. R ## Current progress and next step -TDD RED → GREEN → REFACTOR is complete. The live E2E acceptance passed, baseline recovery was verified, static checks passed, and issue #77 was corrected. The only pending action is the work-unit commit, which requires explicit user authorization. No commit has been created. +TDD RED → GREEN → REFACTOR is complete. The live E2E acceptance passed, baseline recovery was verified, static checks passed, issue #77 was corrected, and work-unit commit `a516ba24f1635ac54b0ada4957fab58616d1badd` was independently verified. Source and tracking evidence commits are complete. From a4885b19829a6c5acf16c2067f9471c203e8c1f3 Mon Sep 17 00:00:00 2001 From: blak0p Date: Thu, 17 Sep 2026 17:55:23 +0200 Subject: [PATCH 3/3] docs(odd): record local udev installation --- odd/tasks/udev-hidraw-uaccess.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/odd/tasks/udev-hidraw-uaccess.md b/odd/tasks/udev-hidraw-uaccess.md index 5d28304..8d045fa 100644 --- a/odd/tasks/udev-hidraw-uaccess.md +++ b/odd/tasks/udev-hidraw-uaccess.md @@ -40,6 +40,7 @@ The issue #77 reporter observed the ordering failure on CachyOS with Hyprland. R - [x] **UDEV-3 — RED, GREEN, and REFACTOR the packaged policy and documentation.** RED observed because the new policy path was absent. GREEN observed after explicit deletion authorization: removed both obsolete `99-...` policies, renamed the correct hidraw policy to `60-attack-shark-x6-hidraw.rules`, and updated the documentation. Focused test passed; `gofmt` completed; `git diff --check` passed. - [x] **UDEV-4 — Verify delivery evidence and clarify issue #77.** Static contract and diff checks passed. Posted and read back the corrective issue comment: https://github.com/blak0p/attack-shark-linux/issues/77#issuecomment-5717223060. It records both the ordering and hidraw-target requirements and the live acceptance evidence. - [x] **UDEV-5 — Create the work-unit commit.** Committed as `a516ba24f1635ac54b0ada4957fab58616d1badd` with `fix(udev): grant uaccess to X6 hidraw nodes`. Native assessment was unavailable/schema-incompatible, so independent verification ran and passed the focused policy test and committed-range diff check. +- [x] **UDEV-6 — Apply the verified policy to the local host.** PASS. Installed `/etc/udev/rules.d/60-attack-shark-x6-hidraw.rules`, removed both superseded local `99-...` rules, reloaded udev, physically replugged the dongle, and observed `user:alejandro:rw-` ACL access on all four X6 hidraw nodes. The temporary rollback backup was removed after verification. ## Acceptance criteria and checks @@ -51,4 +52,4 @@ The issue #77 reporter observed the ordering failure on CachyOS with Hyprland. R ## Current progress and next step -TDD RED → GREEN → REFACTOR is complete. The live E2E acceptance passed, baseline recovery was verified, static checks passed, issue #77 was corrected, and work-unit commit `a516ba24f1635ac54b0ada4957fab58616d1badd` was independently verified. Source and tracking evidence commits are complete. +TDD RED → GREEN → REFACTOR is complete. The live E2E acceptance passed, baseline recovery was verified, static checks passed, issue #77 was corrected, and work-unit commit `a516ba24f1635ac54b0ada4957fab58616d1badd` was independently verified. The final persistent local policy installation also passed. The host-install evidence update remains uncommitted; push and PR creation remain maintainer decisions.