The Game Mode takeover blamed polkit for a group it never named, and prescribed two remedies that cannot work #148

Merged
enricobuehler merged 1 commits from worktree-dm-helper-preflight into main 2026-08-09 21:17:38 +00:00
Owner

Replaces #147, which conflicted — that branch was cut from a stale origin/main and duplicated the gamescope bind work already merged in #144. This is the same first commit, cherry-picked cleanly onto current main.

The defect

Field session on home-nobara-1 (fc44, canary 0.27.0-0.ci12635.g003ce8be): every connect degraded to ATTACH and reported:

the packaged pf-dm-helper polkit action is missing or was denied
(reinstall the punktfunk package, or install the display-manager polkit rule from the docs)

Every clause of that is wrong. Verified on the box: the action is installed, is allow_any, its exec.path annotation matches the installed helper, and pkexec authorized it and ran the helper. The helper refused, and said exactly why:

pf-dm-helper: user 'nobara-user' is not in the 'punktfunk' group — refusing.
  Grant it with: sudo usermod -aG punktfunk nobara-user   (then re-login)

That text never reached the log, because dm_helper ran the helper with .status() — which discards stderr and collapses the exit code to a bool. The one thing that would have ended the investigation in seconds was thrown away at the call site, and the caller then guessed. Neither suggested remedy adds anyone to a group, so a reader who followed both stayed broken.

The upstream cause is packaging. The RPM creates the punktfunk group and adds nobody to it, and its post-install message mentions the group only for "the virtual Steam Deck pad (usbip)" — so a user without a Deck pad correctly skips it and lands here by following the instructions properly. It fails soft (the takeover degrades, the stream still comes up), so there is no error, no failed unit, and nothing in the console. It appears to affect every RPM install.

The fix

  • dm_helper now uses .output() and returns four failure modes that stay distinguishable because they need different fixes: helper not installed · pkexec could not run it · polkit denied it (pkexec's own 126/127) · the helper ran and refused, whose stderr rides through verbatim rather than being re-described. Null stdin, so a pkexec that decides to prompt gets EOF instead of parking a stream thread on a tty read.
  • The same gate gates the linger verb, so on a sessionless host an unjoined user fails there first — the reason is carried there too, or the misdiagnosis just moves one message earlier.
  • A startup preflight says it before a stream is being built rather than during one, gated on five conditions so it cannot nag a box that would never attempt a takeover. It reads membership from the user database rather than this process's groups, deliberately: that is what the helper reads (it runs as root), so usermod -aG satisfies the DM gate immediately and the warning stops.
  • The strings "reinstall the punktfunk package" and "install the display-manager polkit rule from the docs" now appear only where they can actually help.
  • Packaging (rpm/deb/arch) leads with Game Mode, names both grants, and records that creating the group is necessary and not sufficient. Docs get the group as an admonition above the DM-flavor list, a black-screen entry in troubleshooting that says to read the quoted reason first, and per-distro pages no longer frame it as pad-only.

Verification

Confirmed on glass: joining the group made the takeover succeed on the very next connect — freed Steam: stopped the display manager for this stream, first time, after hours of it degrading.

Gates, all re-run against this rebased branch: cargo fmt --all --check; cargo clippy --workspace --all-targets --locked -- -D warnings on linux/amd64; cargo test -p pf-vdisplay 134 passed / 0 failed; scripts/xcheck.sh windows clippy; docs-site bun run build. No Cargo.toml/Cargo.lock changes.

Follow-up worth doing separately

#144's bind arms BindReadOnlyPaths without proving mount namespacing is available, and has no handling for a unit that fails 226/NAMESPACE. On .25 that bind fails while kernel.unprivileged_userns_clone=1 and max_user_namespaces=29006 are both permissive, because AppArmor's apparmor_restrict_unprivileged_userns blocks it — so a sysctl-based check would say "available" and arm a bind that makes the session unit unstartable. A throwaway systemd-run --property=BindReadOnlyPaths=<real value> -- /bin/true probe plus an ExecMainStatus == 226 backstop closes that; it exists on the abandoned branch and can be ported.

Replaces #147, which conflicted — that branch was cut from a stale `origin/main` and duplicated the gamescope bind work already merged in #144. This is the same first commit, cherry-picked cleanly onto current `main`. ## The defect Field session on `home-nobara-1` (fc44, canary `0.27.0-0.ci12635.g003ce8be`): every connect degraded to ATTACH and reported: ``` the packaged pf-dm-helper polkit action is missing or was denied (reinstall the punktfunk package, or install the display-manager polkit rule from the docs) ``` **Every clause of that is wrong.** Verified on the box: the action *is* installed, is `allow_any`, its `exec.path` annotation matches the installed helper, and pkexec authorized it and **ran the helper**. The helper refused, and said exactly why: ``` pf-dm-helper: user 'nobara-user' is not in the 'punktfunk' group — refusing. Grant it with: sudo usermod -aG punktfunk nobara-user (then re-login) ``` That text never reached the log, because `dm_helper` ran the helper with `.status()` — which discards stderr and collapses the exit code to a bool. The one thing that would have ended the investigation in seconds was thrown away at the call site, and the caller then guessed. Neither suggested remedy adds anyone to a group, so a reader who followed both stayed broken. **The upstream cause is packaging.** The RPM *creates* the `punktfunk` group and adds nobody to it, and its post-install message mentions the group only for "the virtual Steam Deck pad (usbip)" — so a user without a Deck pad correctly skips it and lands here **by following the instructions properly**. It fails soft (the takeover degrades, the stream still comes up), so there is no error, no failed unit, and nothing in the console. It appears to affect every RPM install. ## The fix - `dm_helper` now uses `.output()` and returns four failure modes that stay distinguishable because they need different fixes: helper not installed · pkexec could not run it · polkit denied it (pkexec's own 126/127) · **the helper ran and refused**, whose stderr rides through **verbatim** rather than being re-described. Null stdin, so a pkexec that decides to prompt gets EOF instead of parking a stream thread on a tty read. - The same gate gates the `linger` verb, so on a sessionless host an unjoined user fails *there* first — the reason is carried there too, or the misdiagnosis just moves one message earlier. - A **startup preflight** says it before a stream is being built rather than during one, gated on five conditions so it cannot nag a box that would never attempt a takeover. It reads membership from the user database rather than this process's groups, deliberately: that is what the helper reads (it runs as root), so `usermod -aG` satisfies the DM gate immediately and the warning stops. - The strings "reinstall the punktfunk package" and "install the display-manager polkit rule from the docs" now appear only where they can actually help. - Packaging (rpm/deb/arch) leads with Game Mode, names both grants, and records that creating the group is necessary and **not sufficient**. Docs get the group as an admonition above the DM-flavor list, a black-screen entry in troubleshooting that says to read the quoted reason first, and per-distro pages no longer frame it as pad-only. ## Verification **Confirmed on glass**: joining the group made the takeover succeed on the very next connect — `freed Steam: stopped the display manager for this stream`, first time, after hours of it degrading. Gates, all re-run against this rebased branch: `cargo fmt --all --check`; `cargo clippy --workspace --all-targets --locked -- -D warnings` on linux/amd64; `cargo test -p pf-vdisplay` 134 passed / 0 failed; `scripts/xcheck.sh windows clippy`; docs-site `bun run build`. No `Cargo.toml`/`Cargo.lock` changes. ## Follow-up worth doing separately #144's bind arms `BindReadOnlyPaths` **without proving mount namespacing is available**, and has no handling for a unit that fails `226/NAMESPACE`. On `.25` that bind fails while `kernel.unprivileged_userns_clone=1` and `max_user_namespaces=29006` are both permissive, because AppArmor's `apparmor_restrict_unprivileged_userns` blocks it — so a sysctl-based check would say "available" and arm a bind that makes the session unit unstartable. A throwaway `systemd-run --property=BindReadOnlyPaths=<real value> -- /bin/true` probe plus an `ExecMainStatus == 226` backstop closes that; it exists on the abandoned branch and can be ported.
enricobuehler added 1 commit 2026-08-09 21:16:44 +00:00
fix(pf-vdisplay): the takeover blamed polkit for a group it never named, and offered two remedies that cannot work
ci / bun-nix (pull_request) Successful in 33s
ci / web (pull_request) Successful in 1m11s
apple / swift (pull_request) Successful in 1m38s
apple / screenshots (pull_request) Skipped
ci / docs-site (pull_request) Successful in 2m9s
ci / rust-arm64 (pull_request) Successful in 3m25s
android / android (pull_request) Successful in 4m28s
ci / rust (pull_request) Successful in 19m29s
fb309e0262
Field triage on Nobara, 2026-08-09. Every connect degraded to ATTACH — which on that box mirrors a
game-mode session the host never configured, and looked like a black screen on every connect. The
host said:

    the packaged pf-dm-helper polkit action is missing or was denied (reinstall the punktfunk
    package, or install the display-manager polkit rule from the docs)

Every clause of that was wrong. The action was installed, `allow_any`, and its exec.path annotation
matched the installed helper; pkexec authorized it and RAN the helper. The helper refused, and said
exactly why:

    pf-dm-helper: user 'nobara-user' is not in the 'punktfunk' group — refusing.
      Grant it with: sudo usermod -aG punktfunk nobara-user   (then re-login)

That text never reached the log, because `dm_helper` ran the helper with `.status()` — which
discards stderr and collapses the exit code to a bool. The one thing that would have ended the
investigation in seconds was thrown away at the call site, and the caller then guessed. Neither
suggested remedy adds anyone to a group, so a reader who followed both stayed broken and learned the
docs were useless. It fails soft, with no error and no failed unit, so nobody finds it on purpose.

Now: `.output()`, and four failure modes that stay distinguishable because they need different
fixes — helper not installed, pkexec could not run it, polkit denied it (pkexec's own 126/127), and
the helper ran and refused, whose stderr rides through VERBATIM rather than being re-described. Null
stdin too, so a pkexec that decides to prompt gets EOF instead of parking a stream thread on a tty
read.

The same gate gates the `linger` verb, so on a sessionless host an unjoined user fails there first —
carrying the reason there as well, or the misdiagnosis just moves one message earlier.

A new startup preflight says it before a stream is being built rather than during one, gated so it
cannot nag a box that would never attempt a takeover: not root, a display-manager alias exists, a
managed session launcher exists, a packaged helper exists, and the user is not in the group. It reads
membership from the user database rather than this process's groups, deliberately: that is what the
helper reads (it runs as root and resolves the caller from the database), so `usermod -aG` satisfies
the DM gate immediately and the warning stops. Using `getgroups()` would keep warning on a box where
the takeover already works.

Packaging said the group was for "the virtual Steam Deck pad (usbip)" — so anyone without a Deck pad
correctly skipped it and landed here by following instructions properly. All three scriptlets now
lead with Game Mode, name both grants, and record that creating the group is necessary and NOT
sufficient. Docs get the same treatment: the group is an admonition above the DM-flavor list in
gamescope.md, a black-screen entry in troubleshooting.md that tells the reader to read the quoted
reason FIRST, and the per-distro install pages no longer frame it as pad-only.
enricobuehler merged commit d39843a858 into main 2026-08-09 21:17:38 +00:00
enricobuehler deleted branch worktree-dm-helper-preflight 2026-08-09 21:17:42 +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#148