The Game Mode takeover blamed polkit for a group it never named — and on Nobara our patched gamescope could never run at all #147
Closed
enricobuehler
wants to merge 2 commits from
worktree-dm-helper-diagnosis into main
pull from: worktree-dm-helper-diagnosis
merge into: :main
:main
:worktree-dm-helper-diagnosis
:worktree-gamescope-pin-bump-nobara
:worktree-notes-capsysnice-correction
:worktree-bazzite-docs-warning
:worktree-kwin-capability-identification
:worktree-gamescope-rpm-deb-builddeps
:worktree-pyrowave-wire-dead-code
:worktree-release-0260
:worktree-cursor-composite-hardening
:worktree-library-settings-origin-split
:worktree-native-decode-m0
:worktree-july-rpm-and-cursor
:worktree-presenter-cadence-wp1
:worktree-decky-brand-name
:worktree-win-amd-decoder-order
:worktree-stall-ride-through
:fix/pad-audio-wasapi-module-path
:feat/android-pad-audio
:worktree-adl-emul-probe
:worktree-haptics-m12-dry
:worktree-haptics-m9-richfb
:worktree-haptics-m11-settings
:worktree-apple-store-screenshots
:worktree-decky-slim-rework
:worktree-appstore-copy
:worktree-haptics-m10-wire
:worktree-haptics-m8-proto
:worktree-haptics-m7-windows
:worktree-haptics-m6-presenter
:worktree-wire-mtu-resilience
:worktree-haptics-m5-android
:worktree-haptics-m4-apple
:worktree-haptics-m3-rumble-engine
:worktree-android-presenter-023-triage
:worktree-vdisplay-resume-recovery
:worktree-kwin-vout-demirror
:fix/web-console-sweep
:fix/windows-web-console-lifecycle
:chore/windows-rerender-semantics
:fix/pf-encode-phase8
:fix/pf-encode-phase7
:feat/cursor-channel
:feat/desktop-mouse-mode
:feat/plugin-kit
:fix/network-split-standing-latency
:fix/windows-msix-arm64-and-manifest
:fix/encode-medium-tier
:fix/encode-rfi-taint-and-oob
:fix/encode-init-leak-and-twin-drift
:fix/encode-teardown-uaf
:feat/pyrowave-windows
:fix/ubuntu-2404-host-deb
:fix/ios-archive-provisioning
:merge/first-frame-latency
:feat/shared-clipboard-v2
:feat/apple-live-activities-widgets
:debug/touch-finger-logging
:feat/shared-clipboard
:midstream-resize
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two fixes from a field session on
home-nobara-1(fc44, canary0.27.0-0.ci12635.g003ce8be), where every connect produced a black screen. Both are root causes in their own right; the first is what made the second take an hour to find.1. The takeover misdiagnosed itself, and prescribed two remedies that cannot work
Every connect degraded to ATTACH and said:
Every clause of that was wrong. Verified on the box: the action is installed, is
allow_any, itsexec.pathannotation matches the installed helper, and pkexec authorized it and ran the helper. The helper refused, and said exactly why:That text never reached the log, because
dm_helperran 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.The upstream cause is packaging. The RPM creates the
punktfunkgroup and adds nobody, 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. This appears to fail on every RPM install, silently, because the takeover degrades rather than errors.Now:
.output(), with four failure modes that stay distinguishable because they need different fixes — helper not installed, pkexec could not run it, polkit denied it, and the helper ran and refused, whose stderr rides through verbatim. 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. Packaging and docs now lead with Game Mode and record that creating the group is necessary and not sufficient.Confirmed on glass: joining the group made the takeover succeed on the next connect (
freed Steam: stopped the display manager for this stream), first time.2. On Nobara, our patched gamescope could never run
With the takeover working, the session still came up as stock gamescope:
Because it structurally cannot do otherwise. On the box:
grep GAMESCOPE_BIN /usr/share/gamescope-session-plus/gamescope-session-plus→ no matches; line 244 →GAMESCOPECMD="/usr/bin/gamescope \. An absolute hardcoded path, and the escape hatch is never read. No env var and noPATHentry can win.So bind over it, inside the session unit's own mount namespace — the distro still owns the file on disk.
The bind source is the WRAPPER, not the patched binary. Binding the binary would have been a vacuous fix: the flags this mechanism exists to deliver are injected by the wrapper, so a bound binary arrives with no flags and the guard refuses exactly as before.
Applied only where the escape hatch is structurally absent, decided by reading the script, never by distro name — SteamOS and Bazzite keep what already works and take no mount namespace they don't need. Availability is proven with a throwaway
systemd-run --property=BindReadOnlyPaths=<real value> -- /bin/true, which matters: on.25that fails226/NAMESPACEwhile both relevant sysctls are permissive, because AppArmor blocks it. A sysctl check would have armed a bind that cannot work.Verification
/usr/bin/gamescopemanually symlinked to the patched build, which makes any bind test vacuous — and the new code deliberately no-ops in that state (fork-bomb guard: the wrapper execs the real binary, so shadowing the same path would exec itself forever). The box is being reset to a clean install to test this properly.cargo fmt --all --check;cargo clippy --workspace --all-targets --locked -- -D warningson linux/amd64 (non-vacuous —Checking pf-vdisplay);cargo test -p pf-vdisplay134 passed / 0 failed;scripts/xcheck.sh windows clippyclean; docs-sitebun run buildclean. NoCargo.toml/Cargo.lockchanges.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.Pull request closed