mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-08-04 08:43:40 +00:00
112 lines
4.6 KiB
Markdown
112 lines
4.6 KiB
Markdown
# Design Document
|
||
|
||
## 1. Introduction
|
||
|
||
This project provides a safe, general-purpose way to run [Sunshine](https://github.com/LizardByte/Sunshine) and other applications that use `/dev/uinput` **inside containers** — including `systemd-nspawn`, Docker, LXC, Podman, and similar runtimes.
|
||
|
||
Applications like Sunshine require creating virtual input devices (`/dev/uinput`) for keyboards, mice, and controllers.
|
||
Naively bind-mounting `/dev/uinput` from the host into a container breaks isolation: a container could create devices visible to other containers or even the host, leading to unwanted input injection and security risks.
|
||
|
||
`vuinputd` introduces a **mediated `/dev/uinput` proxy** that preserves isolation without kernel changes.
|
||
|
||
---
|
||
|
||
## 2. Architecture
|
||
|
||
Normally, applications open `/dev/uinput` directly to create virtual event devices such as `/dev/input/event9`:
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
uinput apps->>uinput (kernel): open /dev/uinput and setup
|
||
create participant eventx
|
||
uinput (kernel)->>eventx: create /dev/input/eventx
|
||
uinput (kernel)->>libinput/game: announce new device via udev
|
||
libinput/game->>eventx: open /dev/input/eventx
|
||
```
|
||
|
||
vuinputd provides a virtual /dev/vuinput implemented via CUSE (Character Device in Userspace).
|
||
This device can be bind-mounted into a container as /dev/uinput, so applications operate normally:
|
||
```mermaid
|
||
|
||
sequenceDiagram
|
||
box transparent Host
|
||
participant uinput (kernel)
|
||
participant vuinputd
|
||
participant vuinput (host)
|
||
end
|
||
|
||
box transparent Container
|
||
participant uinput (container)
|
||
participant uinput apps
|
||
participant eventX
|
||
participant libinput/game
|
||
end
|
||
|
||
vuinputd->>vuinput (host): create /dev/vuinput with cuse
|
||
uinput apps->>uinput (container): open /dev/uinput and setup
|
||
uinput (container)-->vuinput (host): is equal (bind mount)
|
||
vuinput (host)->>vuinputd: forward data
|
||
vuinputd->>uinput (kernel): forward data
|
||
uinput (kernel)->>eventX: create /dev/input/eventX
|
||
uinput (kernel)->>vuinputd: announce new device via udev
|
||
vuinputd->>libinput/game: announce new device via udev
|
||
libinput/game->>eventX: open /dev/input/eventX
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Design Decisions
|
||
|
||
### 3.1 Where `/dev/uinput` lives
|
||
|
||
* **Decision**: Provide a fake `/dev/vuinput` backed by host proxy. This character device can be bind mounted inside containers to `/dev/uinput`.
|
||
* **Why**: Prevents containers from creating devices visible system-wide.
|
||
|
||
### 3.2 Prevent host from using devices
|
||
|
||
* **Decision**: udev rules strip `ID_INPUT_KEYBOARD` and `ID_INPUT_MOUSE`, set `ID_SEAT=seat_vuinput`.
|
||
* **Why**: Ensures devices are invisible to host input subsystems while still available in containers.
|
||
|
||
### 3.3 udev events in containers
|
||
|
||
* **Decision**: Proxy forwards udev events into the container via netlink.
|
||
* **Why**: Without this, SDL2 and libinput might not recognize devices correctly; with it, containers behave as if devices were created locally.
|
||
|
||
### 3.4 Where to run the proxy
|
||
|
||
* **Decision**: Run proxy on host, one instance per container.
|
||
* **Why**: Only host can safely access `/dev/uinput` and enforce mediation.
|
||
|
||
### 3.5 Security trade-off
|
||
|
||
* **Decision**: Accept that host always sees devices, but enforce rules to stop it consuming them.
|
||
* **Why**: Full input namespaces don’t exist in Linux today; mediation is the practical compromise.
|
||
|
||
### 3.6 Compatibility
|
||
* **Runtimes supported:** Works with systemd-nspawn, Docker, LXC, Podman, and other container engines.
|
||
* **Applications supported:** Any program that writes to `/dev/uinput`, including Sunshine, custom input injectors, and game streaming servers.
|
||
---
|
||
|
||
## 4. Security Considerations
|
||
|
||
`vuinputd` must currently run with **root privileges** to:
|
||
|
||
* Access `/dev/uinput` and create CUSE devices.
|
||
* Send and receive **udev/netlink** messages.
|
||
* Manage per-container device nodes under `/dev/input`.
|
||
|
||
While this design is necessary for mediation, it introduces potential attack surfaces:
|
||
|
||
### ⚠️ Risks
|
||
|
||
* **Privilege escalation:** a compromised container could exploit bugs in the proxy.
|
||
* **Input injection:** if isolation fails, input devices may leak between containers.
|
||
* **Unsafe FUSE/`unsafe` code:** any memory or pointer error could lead to denial-of-service or privilege abuse.
|
||
|
||
### 🛡️ Mitigations (planned / recommended)
|
||
|
||
* [ ] Drop capabilities after startup (e.g. keep only `CAP_SYS_ADMIN` where needed).
|
||
* [ ] Run under a dedicated **system user** (`vuinputd`) with limited filesystem access.
|
||
* [ ] Enforce **container identity** using cgroup, namespace, or pidfd checks.
|
||
* [ ] Use **seccomp** or `systemd` sandboxing (`ProtectSystem`, `ProtectKernelTunables`, `RestrictNamespaces`, etc.).
|
||
* [ ] Eventually migrate to **Rust-native FUSE/Netlink** bindings to remove unsafe dependencies.
|