From 11144bd6941f72d8d9c1b56584db7eccea5a9e92 Mon Sep 17 00:00:00 2001 From: Johannes Leupolz Date: Thu, 8 Jan 2026 21:13:07 +0000 Subject: [PATCH] Add design considerations for #4 --- docs/DESIGN.md | 46 ++++++++++++++++++- vuinputd/src/cuse_device/device_policy.rs | 54 ++++++++++++++--------- 2 files changed, 79 insertions(+), 21 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index f489a12..09fea37 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -499,10 +499,54 @@ As a result: * input devices may interact with the VT layer in unintended ways When a graphical session is active, these issues do not occur: -the compositor, via `systemd-logind`, owns a VT, switches it to `KD_GRAPHICS`, and the VT keyboard handler is **suppressed**. +the compositor, via `systemd-logind`, owns a VT, switches it to `KD_GRAPHICS` and `K_OFF`, and the VT keyboard handler is **suppressed**. The missing piece is a **well-defined fallback** for the “no graphical session” case. +### **Effect of K_OFF in Linux VT subsystem** + +- ioctl KDSKBMODE on /dev/ttyX leads to call of vt_do_kdskbmode +- kb->kbdmode = VC_OFF + +This suppresses the following chains in [keyboard.c](https://github.com/torvalds/linux/blob/master/drivers/tty/vt/keyboard.c) +Lets take Console_1 via ALT+F2 as an example: +- kbd_event (Entry Point) +- kbd_keycode (Translation) + - Job: looks up the Keysym in the keymap based on the current modifier state (ALT+F2 is 0xf501 in [defkeymap.c_shipped](https://github.com/torvalds/linux/blob/master/drivers/tty/vt/defkeymap.c_shipped)) + - type = KTYP(keysym) takes the first 16 bits (which is 0xf5). + - type -= 0xf0. (which is 0x05). Note hat the kernel uses the ** offset** `0xf0` to differentiate between characters and special handlers in the keymap. When `type -= 0xf0` is called, it "normalizes" the keysym into an index for the `k_handler` array. + - index = KVAL(keysym) takes the last 16 bits (which is 0x01) + - return if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT), so suppression happens here. + - Note that K_HANDLERS[type] == K_HANDLERS[0x05] == k_cons. + - if no suppression: call (*k_handler[type])(vc, KVAL(keysym), !down), which is k_cons(vc,0x01) + +Lets take Decr_Console via ALT+Left as second example: +- kbd_event (Entry Point) +- kbd_keycode (Translation) + - Job: looks up the Keysym in the keymap based on the current modifier state (ALT+Left is 0xf210 in [defkeymap.c_shipped](https://github.com/torvalds/linux/blob/master/drivers/tty/vt/defkeymap.c_shipped)) + - type = KTYP(keysym) takes the first 16 bits (which is 0xf2). + - type -= 0xf0. (which is 0x02) + - index = KVAL(keysym) takes the last 16 bits (which is 0x10) + - return if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT), so suppression does *not* happen here. + - Note that K_HANDLERS[type] == K_HANDLERS[0x02] == k_spec. + - call (*k_handler[type])(vc, KVAL(keysym), !down), which is k_spec(vc,0x10) +- k_spec (Handling) + - Condition if ((... || kbd->kbdmode == VC_OFF) && value != KVAL(K_SAK)) evaluates to false, so suppression happens here + - if no suppression: call fn_handler[value](...) which is fn_dec_console(...) + +In the KT_SHIFT-case of "return if ((raw_mode || kbd->kbdmode == VC_OFF) && type != KT_SPEC && type != KT_SHIFT", nothing interesting happens in our case: it might enable and disable caps lock. This means uinput can still enable disable caps, which is a bit odd, but nothing tragic in our use cases. + +sysrq has an own handler. It is not affected by K_OFF. +https://github.com/torvalds/linux/blob/master/drivers/tty/sysrq.c#L1048 + +Raw mode is not relevant. + +### The Risk of Non-Standard or User-Loaded Keymaps + +While `vuinputd` relies on the default kernel keymap logic for its internal filtering, it is important to note that the host's active keymap can be modified at runtime (e.g., via `loadkeys` or `systemd-vconsole-setup`). Because the kernel's `K_OFF` logic (triggered by `KDSKBMODE`) explicitly whitelists the `K_SAK` (Secure Attention Key) keysym, any user-defined key combination mapped to `SAK` will bypass the kernel's own input suppression. + +To further mitigate these risks, `vuinputd` could be extended to parse the host's active keymap during startup, allowing the sanitizer to dynamically identify and filter any physical keycode mapped to a sensitive keysym like `K_SAK`. This is currently not planned. Alternatively, on systems dedicated to containerization where full host TTY access is not required, administrators can load a "hardened" keymap stripped of all `Console_N`, `Boot`, and `SAK` assignments. By combining a minimized host keymap with `vuinputd`'s CUSE-level filtering, the system achieves a robust "Defense in Depth" that protects against both accidental triggers and intentional container escapes. + ### **Decision** `fallbackdm` is implemented as a **logind-managed fallback graphical session**. diff --git a/vuinputd/src/cuse_device/device_policy.rs b/vuinputd/src/cuse_device/device_policy.rs index 5385582..9a622c2 100644 --- a/vuinputd/src/cuse_device/device_policy.rs +++ b/vuinputd/src/cuse_device/device_policy.rs @@ -93,13 +93,20 @@ fn is_allowed_in_sanitized_mode(keytracker: &mut KeyTracker, event: &input_event return false; } - // 3. Block CAD (Ctrl + Alt + Del) + // 3. Block CAD (Ctrl + Alt + Del). // Block basically all Boot from defkeymap.map if alt_down && ctrl_down && (code == KEY_DELETE || code == KEY_KPDOT) { return false; } - // 4. Block standalone dangerous keys + // 4. Block SAK (CTRL+ALT+PAUSE). seems not to be mapped + // This one is crucial, because it is allowed even in K_OFF mode. + // https://github.com/torvalds/linux/blob/master/drivers/tty/vt/keyboard.c + // It seems not to be mapped by default, but close the potential hole if a + // distro follows the ancient https://www.kernel.org/doc/Documentation/SAK.txt + + + // 5. Block standalone dangerous keys match code { KEY_POWER | KEY_SLEEP | KEY_WAKEUP => return false, _ => {} @@ -108,26 +115,33 @@ fn is_allowed_in_sanitized_mode(keytracker: &mut KeyTracker, event: &input_event true } -fn is_allowed_in_strict_gamepad_mode(keytracker: &mut KeyTracker, event: &input_event) -> bool { - let type_ = event.type_; - let code = event.code; +fn is_allowed_in_strict_gamepad_mode( + _keytracker: &mut KeyTracker, + event: &input_event, +) -> bool { + match event.type_ { + EV_SYN => true, - if type_ == EV_SYN { - return true; - } - if type_ == EV_ABS { - return true; - } - if type_ == EV_FF { - return true; - } + // Analog sticks, triggers + EV_ABS => true, - if type_ == EV_KEY { - return match code { - BTN_SOUTH..BTN_THUMBR => true, - BTN_DPAD_UP..BTN_GRIPR2 => true, + // Force feedback + EV_FF => true, + + // Digital buttons only + EV_KEY => match event.code { + // Standard gamepad face + shoulder + stick buttons + BTN_SOUTH..=BTN_THUMBR => true, + + // D-Pad + extended gamepad buttons (triggers, paddles) + BTN_DPAD_UP..=BTN_GRIPR2 => true, + + // Everything else is rejected (KEY_*, mouse buttons, etc.) _ => false, - }; + }, + + // Explicitly reject everything else (EV_REL, EV_MSC, etc.) + _ => false, } - false } +