The virtual Steam Deck pad's udev rule names a group that four of six install paths never create #123

Merged
enricobuehler merged 1 commits from worktree-steamdeck-group-and-secret-gaps into main 2026-08-08 16:00:51 +00:00
Owner

A user on SteamOS asked whether the manual commands in the 0.25.0 release notes are their job or the update script's. Answering it turned up a real bug behind the question.

The bug

scripts/60-punktfunk.rules chgrps the usbip vhci attach/detach nodes to a dedicated punktfunk group — deliberately not input, because writing attach hands the kernel a caller-supplied socket fd and materialises an arbitrary emulated USB device (security-review 2026-08-05 M-4).

Four of the six host install paths shipped that rule in 0.25.0 without ever creating the group. The chgrp then fails, the nodes stay root:root 0644, and the virtual Steam Deck pad silently never attaches — it degrades to a generic Xbox 360 controller with nothing in any log the user would look at. The remedy the release notes print fails too, with usermod: group 'punktfunk' does not exist.

Path Was Now
Arch / CachyOS, upgraded post_upgrade() called only _ensure_update_grouppost_install was correct, so only boxes that reached 0.25.0 via pacman -Syu are affected also calls _ensure_punktfunk_group
NixOS no users.groups.punktfunk at all, although host.users' own option description already promised the usbip/vhci pad declares the group; host.users joins both
Bazzite sysext a group is host state (/etc/group) and cannot ride an image, and the deb/rpm scriptlets that would create it never run there groupadd in post_merge
Steam Deck source install install.sh / update.sh handled input only create the group and join it

deb and rpm were correct throughout — they run one postinst / %post on install and upgrade alike.

Why the Deck scripts join the group rather than print the command

The distro packages print it because they cannot know whether the operator wants that pad. Running scripts/steamdeck/install.sh is the statement "make my Deck a host with native pad passthrough", and the script already auto-joins input (systemwide input injection). The alternative is exactly the silent failure being fixed. It warns loudly and documents gpasswd -d "$USER" punktfunk. The per-distro guides keep it opt-in. Flagging this one explicitly in case you'd rather it only printed the command.

Also: web.env secret hygiene on the Deck

install.sh's chmod 600 sat inside the create-only branch, despite a comment describing it as "the idempotent belt for a pre-existing file" — so it only ever ran on files just written 0600 anyway. update.sh never touched the config dir at all. A Deck set up once and only ever updated since therefore kept web.env at the ambient umask (0644), with the console login password and session secret readable by every local account.

Both scripts now harden ~/.config/punktfunk to 0700 and web.env to 0600 on every run, and say so loudly — a chmod does not un-leak an already-readable secret, so the password still needs rotating. The 0.25.0 notes' "consider rotating your console password" is upgraded to an actual instruction with the commands.

A set -e trap in the fix itself

Both group blocks are if ensure_group ...; then rather than ensure_group || true. A failed groupadd must not fall through to a usermod against a nonexistent group: under set -e that aborted install.sh after the long build and before the services were installed, and update.sh before its service restart — leaving the host down. Measured in a container: old shape exits 6 and never reaches the restart, new shape warns and continues.

Docs

The group was documented nowhere on the docs site — only in the release notes and CHANGELOG. Now covered in arch.md, fedora.md, ubuntu.md, bazzite.md, install.md, steamos-host.md, all five uninstall paths, and a new troubleshooting entry for the actual symptom ("the pad arrives as an Xbox 360 controller instead of a Steam Deck") with the four checks that isolate it.

docs/releases/v0.25.0.md gains the group 'punktfunk' does not exist caveat and scopes the bullet to hosts (a machine you only stream from needs nothing). CHANGELOG.md records the known issue against the breaking change that introduced it.

The live Gitea release body still carries the old 0.25.0 text. docs/releases/README.md sanctions editing the file after the tag, but syncing it needs a re-announce or an API PATCH — left alone here since that is outward-facing.

Answering the original question

On a Deck, once this lands, the user runs nothing by hand: update.sh creates and joins the group and fixes the file modes. Stock SteamOS runs no firewall, so the 47993 bullet does not apply there either. Rotating the console password after a pre-0.25.0 install is the one item still on them.

Verification

  • bash -n on all four edited scripts.
  • Arch post_upgrade() sourced and driven in a container: creates the group, idempotent on a second run, no regression on punktfunk-update.
  • ensure_group and both membership branches exercised in a container, including a control that reproduces the original bugchgrp to a missing group fails and leaves the node root:root 0644.
  • The find -perm /0077 probe across 0644 / 0640 / 0604 / 0600 / 0400 on GNU findutils (correctly flags the first three), plus the missing-file case not aborting under set -e.
  • nix flake check --no-build — the exact gate in nix.yml, which packaging/nix/** triggers — passes, including checking NixOS module 'nixosModules.default'. A full NixOS eval returns aliceExtraGroups: ["input","punktfunk"] with both groups declared.
  • docs-site bun run build + bun run lint (tsc --noEmit) clean.

Not verified on glass: no Deck, Arch, Bazzite or NixOS box was touched. The container tests cover the group and permission logic, not a real vhci attach.

A user on SteamOS asked whether the manual commands in the 0.25.0 release notes are their job or the update script's. Answering it turned up a real bug behind the question. ## The bug `scripts/60-punktfunk.rules` `chgrp`s the usbip vhci `attach`/`detach` nodes to a dedicated `punktfunk` group — deliberately not `input`, because writing `attach` hands the kernel a caller-supplied socket fd and materialises an arbitrary emulated USB device (security-review 2026-08-05 M-4). **Four of the six host install paths shipped that rule in 0.25.0 without ever creating the group.** The `chgrp` then fails, the nodes stay `root:root 0644`, and the virtual Steam Deck pad silently never attaches — it degrades to a generic Xbox 360 controller with nothing in any log the user would look at. The remedy the release notes print fails too, with `usermod: group 'punktfunk' does not exist`. | Path | Was | Now | |---|---|---| | **Arch / CachyOS, upgraded** | `post_upgrade()` called only `_ensure_update_group` — `post_install` was correct, so only boxes that reached 0.25.0 via `pacman -Syu` are affected | also calls `_ensure_punktfunk_group` | | **NixOS** | no `users.groups.punktfunk` at all, although `host.users`' own option description already promised the usbip/vhci pad | declares the group; `host.users` joins both | | **Bazzite sysext** | a group is host state (`/etc/group`) and cannot ride an image, and the deb/rpm scriptlets that would create it never run there | `groupadd` in `post_merge` | | **Steam Deck source install** | `install.sh` / `update.sh` handled `input` only | create the group **and** join it | deb and rpm were correct throughout — they run one `postinst` / `%post` on install and upgrade alike. ### Why the Deck scripts join the group rather than print the command The distro packages print it because they cannot know whether the operator wants that pad. Running `scripts/steamdeck/install.sh` *is* the statement "make my Deck a host with native pad passthrough", and the script already auto-joins `input` (systemwide input injection). The alternative is exactly the silent failure being fixed. It warns loudly and documents `gpasswd -d "$USER" punktfunk`. The per-distro guides keep it opt-in. **Flagging this one explicitly in case you'd rather it only printed the command.** ## Also: `web.env` secret hygiene on the Deck `install.sh`'s `chmod 600` sat *inside* the create-only branch, despite a comment describing it as "the idempotent belt for a pre-existing file" — so it only ever ran on files just written `0600` anyway. `update.sh` never touched the config dir at all. A Deck set up once and only ever updated since therefore kept `web.env` at the ambient umask (`0644`), with the console login password and session secret readable by every local account. Both scripts now harden `~/.config/punktfunk` to `0700` and `web.env` to `0600` on every run, and say so loudly — a chmod does not un-leak an already-readable secret, so the password still needs rotating. The 0.25.0 notes' "consider rotating your console password" is upgraded to an actual instruction with the commands. ## A `set -e` trap in the fix itself Both group blocks are `if ensure_group ...; then` rather than `ensure_group || true`. A failed `groupadd` must not fall through to a `usermod` against a nonexistent group: under `set -e` that aborted `install.sh` *after* the long build and before the services were installed, and `update.sh` before its service restart — leaving the host down. Measured in a container: old shape exits 6 and never reaches the restart, new shape warns and continues. ## Docs The group was documented **nowhere** on the docs site — only in the release notes and CHANGELOG. Now covered in `arch.md`, `fedora.md`, `ubuntu.md`, `bazzite.md`, `install.md`, `steamos-host.md`, all five uninstall paths, and a new troubleshooting entry for the actual symptom ("the pad arrives as an Xbox 360 controller instead of a Steam Deck") with the four checks that isolate it. `docs/releases/v0.25.0.md` gains the `group 'punktfunk' does not exist` caveat and scopes the bullet to hosts (a machine you only stream *from* needs nothing). `CHANGELOG.md` records the known issue against the breaking change that introduced it. ⚠ **The live Gitea release body still carries the old 0.25.0 text.** `docs/releases/README.md` sanctions editing the file after the tag, but syncing it needs a re-announce or an API PATCH — left alone here since that is outward-facing. ## Answering the original question On a Deck, once this lands, the user runs nothing by hand: `update.sh` creates and joins the group and fixes the file modes. Stock SteamOS runs no firewall, so the 47993 bullet does not apply there either. Rotating the console password after a pre-0.25.0 install is the one item still on them. ## Verification - `bash -n` on all four edited scripts. - Arch `post_upgrade()` sourced and driven in a container: creates the group, idempotent on a second run, no regression on `punktfunk-update`. - `ensure_group` and both membership branches exercised in a container, **including a control that reproduces the original bug** — `chgrp` to a missing group fails and leaves the node `root:root 0644`. - The `find -perm /0077` probe across `0644 / 0640 / 0604 / 0600 / 0400` on GNU findutils (correctly flags the first three), plus the missing-file case not aborting under `set -e`. - `nix flake check --no-build` — the exact gate in `nix.yml`, which `packaging/nix/**` triggers — passes, including `checking NixOS module 'nixosModules.default'`. A full NixOS eval returns `aliceExtraGroups: ["input","punktfunk"]` with both groups declared. - `docs-site` `bun run build` + `bun run lint` (`tsc --noEmit`) clean. Not verified on glass: no Deck, Arch, Bazzite or NixOS box was touched. The container tests cover the group and permission logic, not a real vhci attach.
enricobuehler added 1 commit 2026-08-08 15:58:14 +00:00
fix(packaging): create the punktfunk group everywhere the udev rule needs it
ci / bun-nix (pull_request) Successful in 29s
ci / docs-site (pull_request) Successful in 1m37s
ci / web (pull_request) Successful in 2m39s
ci / rust-arm64 (pull_request) Successful in 4m10s
ci / rust (pull_request) Successful in 6m50s
nix / flake (pull_request) Failing after 23m28s
62a6fa9fac
60-punktfunk.rules chgrp's the usbip vhci attach/detach nodes to a dedicated
`punktfunk` group (security-review 2026-08-05 M-4: writing `attach` materialises
an arbitrary emulated USB device, so it must not ride on `input`). Four of the
six install paths shipped that rule in 0.25.0 without ever creating the group.
chgrp then failed, the nodes stayed root:root 0644, and the virtual Steam Deck
pad silently never attached — while `usermod -aG punktfunk` failed outright with
"group 'punktfunk' does not exist".

Affected and fixed:

  * arch  — post_upgrade() called only _ensure_update_group, so every box that
            reached 0.25.0 by `pacman -Syu` missed it; post_install was correct.
  * nix   — no users.groups.punktfunk at all, though host.users' own description
            already promised the usbip/vhci pad. Declares it now and adds
            host.users to both groups.
  * bazzite sysext — a group is host state and cannot ride an image, and the
            deb/rpm scriptlets that would create it never run there.
  * steamdeck install.sh/update.sh — handled `input` only. Both now create the
            group and join it: running that script IS the statement "make my
            Deck a host with native pad passthrough".

deb and rpm were correct throughout (one postinst/%post for install + upgrade).

Also on the Deck path: web.env secret hygiene. install.sh's `chmod 600` sat
inside the create-only branch despite a comment calling it "the idempotent belt
for a pre-existing file", and update.sh never touched the config dir at all — so
an install set up once and only updated since kept web.env world-readable
(0644) with the console password and session secret in it. Both scripts now
harden ~/.config/punktfunk to 0700 and web.env to 0600 on every run, and say so
loudly, because a chmod does not un-leak an already-readable secret: the
password still needs rotating.

Both group blocks are `if ensure_group ...` rather than `ensure_group || true`:
a failed groupadd must not fall through to a usermod against a nonexistent
group, which under `set -e` aborted install.sh after the long build and
update.sh before the service restart (verified: exit 6, no restart).

Docs: the group is now documented where people actually look — the per-distro
guides, install.md, steamos-host.md, a new troubleshooting entry for "pad
arrives as an Xbox 360 controller", and the uninstall pages. The 0.25.0 notes
gain the "group does not exist" caveat and turn the password bullet from
"consider rotating" into a real instruction, and CHANGELOG records the known
issue against the breaking change that introduced it.

Verified: bash -n on all four scripts; the arch scriptlet's post_upgrade driven
in a container (creates the group, idempotent on re-run); the ensure_group
helper and both membership branches, including a control that reproduces the
original bug (chgrp to a missing group leaves the node root:root 0644); the
find -perm /0077 probe across 0644/0640/0604/0600/0400 on GNU findutils;
`nix flake check --no-build` (the exact CI gate) and a NixOS eval showing
alice.extraGroups == ["input","punktfunk"]; docs-site build + typecheck.
enricobuehler merged commit 0cd946acb5 into main 2026-08-08 16:00:51 +00:00
enricobuehler deleted branch worktree-steamdeck-group-and-secret-gaps 2026-08-08 16:00:57 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: unom/punktfunk#123