ShadowBlip / InputPlumber

Open source input router and remapper daemon for Linux
GNU General Public License v3.0
93 stars 12 forks source link

Feature: Support running InputPlumber unprivileged #202

Open R1kaB3rN opened 1 month ago

R1kaB3rN commented 1 month ago

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 commented 1 month ago

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 commented 1 month ago

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

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).

braiam commented 2 weeks ago

Would a patch to upstream kernel be a solution to support this use case?