Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Support running InputPlumber unprivileged #202

Open
R1kaB3rN opened this issue Sep 14, 2024 · 2 comments
Open

Feature: Support running InputPlumber unprivileged #202

R1kaB3rN opened this issue Sep 14, 2024 · 2 comments
Labels
enhancement New feature or request

Comments

@R1kaB3rN
Copy link

Description

I'm interested in making InputPlumber available for clients through umu-launcher to fill in the gap of users unable to have their game controllers work, with the goal of the service being started by the client or umu-launcher at game launch in an unprivileged context.

Problem

InputPlumber requires to be started and enabled as a privileged user to fulfill all of its tasks properly, making it both inaccessible for Flatpak applications and inconvenient as it would require clients to prompt their users for elevated privileges in the non-Flatpak case. Attempting to execute the inputplumber binary unprivileged results in:


[2024-09-14T19:03:19Z INFO  inputplumber] Starting InputPlumber v0.35.2
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(25))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(27))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(29))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(31))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(33))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(35))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(37))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(39))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(41))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(43))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(45))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(47))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(49))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(51))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(53))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(55))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(57))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(59))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(61))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(63))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(65))
[2024-09-14T19:03:19Z ERROR inputplumber::input::manager] Failed to run task to list device configs: JoinError::Cancelled(Id(67))

Due to this limitation, clients cannot simply run the binary like gamescope-dbus. As a result, users of popular launchers (e.g., Heroic Games Launcher) continue to depend on other clients or use other means to have control over their input devices (e.g., Steam Deck users adding a client app as a non-Steam game in Steam. In other cases, users may revert to using an old version of Wine due to their input devices being incorrectly mapped when running games through Proton.

Being able to run InputPlumber as an unprivileged user would avoid any manual intervention from the user or dependency on external apps to control their input devices.

Proposal

Support running InputPlumber as an unprivileged user or at least a subset of InputPlumber's feature set in that case. After bringing this up to @ShadowApex, they said it should theoretically be possible as long as there are polkits in place. However, the policies would need to be created and installed as the root user, making this only doable for distribution packages. I'm wondering if there's another way or if this feature is just impossible to support given the current design?

Any thoughts/suggestions on this are appreciated.

@pastaq
Copy link
Contributor

pastaq commented Sep 15, 2024

This is an interesting proposal but it comes with quite a big challenge. The reason it runs as a root daemon is the need to hide the original input devices from userspace apps to avoid duplicated input. The way we do this is by inserting a device specific udev rule into /run/udev that sets the original device's sysfs file descriptors to root only, then re-triggering udev on that device. This accomplishes two things:

1.) Blocks userspace applications from seeing the original device as they no longer have read permission for the device.
2.) It breaks IOCTL for applications that have already authenticated on the file descriptor.

breaking IOCTL is very important as there is no built-in way for us to inform IOCTL that permissions have changed. You can verify this yourself by opening a 777 permissions file as a regular user, then in a nother terminal changing the permissions to 400. You will still be able to write to the file until you close it.

The only other way that we've found to do both things is to move the file descriptors to /dev/input/.hidden and both approaches require root access to do so. Anything that alters or moves device nodes requires some sort of privilege escalation. Unfortunately polkit, sudoers, and gksudo are explicitly disabled by both bwrap and firejail, as SUID will fail, by design, from inside the jail no matter what.

One possibility might be to run a second nested jail inside the flatpak jail to filter the device nodes to only the ones provided by InputPlumber. I have no idea how difficult that would be or if it is even possible since running the jail also requires SUID, at least for firejail. I don't remember if bwrap uses SUID to start a jail.

Currently the only way I know how to do something similar to what you want without installing to the system is using systemd-ext packaging.

@ShadowApex
Copy link
Contributor

To expand on this, these are the different patterns we know about to hide the source input device from applications:

  • Write udev rule to /run/udev to block source input device (requires root). As mentioned, this is what we currently do in InputPlumber.
  • Move input dev nodes (e.g. /dev/input/event0) to another path to prevent applications from finding the device (requires root). This is what we used to do, but in some cases, applications would open the file descriptor before we could actually move the device, causing the app to read inputs from both the source device and the virtual input device.
  • Set the SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES and/or SDL_HINT_HIDAPI_IGNORE_DEVICES environment variables when launching games to inform SDL to ignore the physical source devices and only use the virtual input devices (no root required). This is what Steam Input does, but this technique only works with games that are using SDL to manage input devices. It also requires coordination with whatever launcher is being used (i.e. Heroic Games Launcher, OpenGamepadUI, Lutris, etc.). We've tried this technique with some success, but there's been problems, especially for native games that do not use SDL to manage input devices.
  • Launch the game using bwrap (or similar sandbox) and only expose the virtual input devices that InputPlumber creates (no root required). This would theoretically work with all input implementations, but would also require launcher coordination so games are launched in a sandbox, and a secondary process would be responsible for exposing the virtual devices to the game's sandbox. I have tried doing this in the past, but could never get it to work correctly. My past attempts were trying to use bwrap to create a fake /dev directory that we could bind mount into the sandbox to selectively expose input devices, but I think the games just wouldn't see any input devices.

We're open to other ideas if there's another technique we could use. Not the best solution, but one option could be to check if the InputPlumber service is available, and if not, the launcher could recommend to the user that they install InputPlumber through their package manager (or systemd extension).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants