mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-18 00:45:07 +00:00
parent
f8bdb04d7e
commit
5277a13904
7 changed files with 93 additions and 5 deletions
|
|
@ -503,8 +503,6 @@ the compositor, via `systemd-logind`, owns a VT, switches it to `KD_GRAPHICS`, a
|
|||
|
||||
The missing piece is a **well-defined fallback** for the “no graphical session” case.
|
||||
|
||||
---
|
||||
|
||||
### **Decision**
|
||||
|
||||
`fallbackdm` is implemented as a **logind-managed fallback graphical session**.
|
||||
|
|
@ -518,7 +516,6 @@ It runs only when **no other graphical session is active** on the seat and exist
|
|||
`fallbackdm` itself does **not** manipulate VTs, perform `KDSETMODE` ioctls, or access `/dev/tty` directly.
|
||||
All VT and seat handling is delegated to `systemd-logind` **via a standard PAM session**.
|
||||
|
||||
---
|
||||
|
||||
### **Behavior and Lifecycle**
|
||||
|
||||
|
|
@ -535,7 +532,6 @@ All VT and seat handling is delegated to `systemd-logind` **via a standard PAM s
|
|||
|
||||
`fallbackdm` is non-interactive by design but may display **minimal status information** in the future.
|
||||
|
||||
---
|
||||
|
||||
### **Rationale**
|
||||
|
||||
|
|
@ -551,7 +547,6 @@ This design:
|
|||
|
||||
Conceptually, `fallbackdm` acts as a **headless placeholder graphical session** that ensures consistent system behavior even when no real graphical environment is running.
|
||||
|
||||
---
|
||||
|
||||
### **Alternatives Considered**
|
||||
|
||||
|
|
@ -566,6 +561,22 @@ Conceptually, `fallbackdm` acts as a **headless placeholder graphical session**
|
|||
|
||||
---
|
||||
|
||||
### 3.12 Device Policies & Input Sanitization
|
||||
|
||||
Exposing raw access to `/dev/uinput` inside a container introduces significant security risks. A malicious process could theoretically emulate a keyboard to execute "BadUSB"-style attacks, trigger kernel-level commands (Magic SysRq), or switch Virtual Terminals (VT) to escape the graphical session.
|
||||
|
||||
To mitigate this, `vuinputd` implements an **Active Filtering Layer** (CUSE middleware) that enforces strict device policies before requests reach the host kernel. This is controlled via the `--device-policy` flag.
|
||||
|
||||
The filtering operates on two levels (Defense in Depth):
|
||||
|
||||
1. **Capability Filtering (`ioctl`):** During device creation, `vuinputd` inspects `UI_SET_KEYBIT`, `UI_SET_RELBIT`, etc. If a container requests capabilities forbidden by the active policy (e.g., a gamepad trying to claim it has a SysRq key), the request is silently ignored or rejected. The resulting device on the host simply lacks those hardware capabilities.
|
||||
2. **Event Filtering (`write`):** At runtime, `vuinputd` inspects the stream of input events. It maintains internal state (tracking modifiers like `Alt` or `Ctrl`) to detect and drop dangerous sequences (e.g., `Alt` + `F1-F12` for VT switching) that the capability filter alone cannot block.
|
||||
|
||||
**Supported Policies:**
|
||||
|
||||
* **`strict-gamepad` (Whitelist):** Designed for console-like isolation. It strictly permits only Gamepad/Joystick events (`EV_KEY` buttons, `EV_ABS` axes). It proactively blocks `EV_REL` (mouse movement) and `ABS_MT` (multitouch), effectively "neutering" complex controllers (like DualSense or Wiimotes) so they cannot be used to hijack the host mouse cursor.
|
||||
* **`sanitized` (Blacklist):** Designed for desktop gaming. It allows standard Keyboard and Mouse input but strictly filters dangerous keys (`KEY_SYSRQ`, `KEY_POWER`) and host-management shortcuts (VT switching, CAD), providing a safe "sandboxed keyboard."
|
||||
|
||||
## 4. Security Considerations
|
||||
|
||||
`vuinputd` must currently run with **root privileges** to:
|
||||
|
|
|
|||
3
vuinputd/src/cuse_device/device_policy.rs
Normal file
3
vuinputd/src/cuse_device/device_policy.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
pub mod device_policy;
|
||||
pub mod state;
|
||||
pub mod vuinput_ioctl;
|
||||
pub mod vuinput_open;
|
||||
|
|
|
|||
|
|
@ -18,11 +18,31 @@ pub struct VuInputDevice {
|
|||
pub devnode: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KeyTracker {
|
||||
pub left_alt_down: bool,
|
||||
pub right_alt_down: bool,
|
||||
pub left_ctrl_down: bool,
|
||||
pub right_ctrl_down: bool,
|
||||
}
|
||||
|
||||
impl KeyTracker {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
left_alt_down: false,
|
||||
right_alt_down: false,
|
||||
left_ctrl_down: false,
|
||||
right_ctrl_down: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VuInputState {
|
||||
pub file: File,
|
||||
pub requesting_process: RequestingProcess,
|
||||
pub input_device: Option<VuInputDevice>,
|
||||
pub keytracker: KeyTracker,
|
||||
}
|
||||
|
||||
#[derive(Debug, Eq, Hash, PartialEq, Clone)]
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ pub unsafe extern "C" fn vuinput_open(
|
|||
file: v,
|
||||
requesting_process,
|
||||
input_device: None,
|
||||
keytracker: KeyTracker::new()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
|||
45
vuinputd/src/global_config.rs
Normal file
45
vuinputd/src/global_config.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// Author: Johannes Leupolz <dev@leupolz.eu>
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use std::sync::OnceLock;
|
||||
|
||||
// --- 1. Define the Global State Container ---
|
||||
// This struct is extensible. You can add more global settings here later.
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalConfig {
|
||||
pub policy: DevicePolicy,
|
||||
}
|
||||
|
||||
// The actual static variable. It starts empty and is set once in main().
|
||||
pub static CONFIG: OnceLock<GlobalConfig> = OnceLock::new();
|
||||
|
||||
// --- 2. Define the Policy Enum ---
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum, Default)]
|
||||
#[clap(rename_all = "kebab-case")] // This ensures StrictGamepad becomes "strict-gamepad"
|
||||
pub enum DevicePolicy {
|
||||
/// Allow all device capabilities
|
||||
None,
|
||||
/// Default: Allow keyboards/mice but block dangerous keys (SysRq, VT switching)
|
||||
#[default]
|
||||
Sanitized,
|
||||
/// Only allow Gamepad-like devices. Block mice and keyboards.
|
||||
StrictGamepad,
|
||||
}
|
||||
|
||||
pub fn initialize_global_config(device_policy: &DevicePolicy) {
|
||||
if CONFIG
|
||||
.set(GlobalConfig {
|
||||
policy: device_policy.clone(),
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
eprintln!("Failed to initialize global config");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_device_policy<'a>() -> &'a DevicePolicy {
|
||||
&CONFIG.get().unwrap().policy
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ pub mod cuse_device;
|
|||
use crate::cuse_device::state::{initialize_dedup_last_error, initialize_vuinput_state};
|
||||
use crate::cuse_device::vuinput_make_cuse_ops;
|
||||
use crate::cuse_device::vuinput_open::VUINPUT_COUNTER;
|
||||
use crate::global_config::DevicePolicy;
|
||||
use crate::jobs::monitor_udev_job::MonitorBackgroundLoop;
|
||||
|
||||
pub mod process_tools;
|
||||
|
|
@ -42,6 +43,7 @@ use crate::process_tools::*;
|
|||
|
||||
pub mod actions;
|
||||
|
||||
pub mod global_config;
|
||||
pub mod jobs;
|
||||
pub mod vt_tools;
|
||||
|
||||
|
|
@ -89,6 +91,10 @@ struct Args {
|
|||
Loss of local access may require recovery via SSH or a rescue boot."
|
||||
)]
|
||||
pub vt_guard: bool,
|
||||
|
||||
/// Enforce a device policy on created devices
|
||||
#[arg(long, value_enum, default_value_t)]
|
||||
device_policy: DevicePolicy,
|
||||
}
|
||||
|
||||
fn validate_args(args: &Args) -> Result<(), String> {
|
||||
|
|
@ -181,6 +187,7 @@ fn main() -> std::io::Result<()> {
|
|||
check_permissions().expect("failed to read the capabilities of the vuinputd process");
|
||||
vt_tools::check_vt_status();
|
||||
|
||||
global_config::initialize_global_config(&args.device_policy);
|
||||
initialize_vuinput_state();
|
||||
VUINPUT_COUNTER.set(AtomicU64::new(3)).expect(
|
||||
"failed to initialize the counter that provides the values of the CUSE file handles",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue