Hello, Brad, folks.
I'd like to make a feature request for grsec, that I hope you'll agree to implement sooner or later.
The scope of the feature is physical screen locking. Locking serial console access or preventing VNC(-alike) access to any locked X11 session is outside of the scope.
The goal is to provide a robust and as-simple-as-possible API for uniform screen locking, so users could:
- lock all their screens at once on a physical machine regardless of the UI they use: text console, X11 or Wayland (read: anything that runs on a /dev/tty[0-9]+ and uses display and input devices directly attached to the machine)
- use a single, trusted locker with as-simple-as-possible implementation so the risks of failure of the locker or leaving the machine semi locked (X11 is locked, but a console session is not, or the reverse) would be minimal
- lock their screen/tmux sessions in a uniform way without the need in detaching screen/tmux and reattaching it later when the machine is unlocked
- have the locker implemented either as a console application or a GUI application, both in the same way, more or less
Suggested kernel API and behaviour details:
1. Provide sysctl entry kernel.grsecurity.screen_lock_tty with the initial value of -1, writable by superuser/CAP_SYS_ADMIN only or by a user in a special group (configured at kernel build time and optionally tuned by another sysctl entry kernel.grsecurity.screen_lock_group).
2. Provide read-only sysctl entry kernel.grsecurity.screen_last_tty with the initial value of -1 readable by superuser/CAP_SYS_ADMIN processes (or by processes in kernel.grsecurity.screen_lock_group).
3. When some number N >= 0 is being written to kernel.grsecurity.screen_lock_tty, and there is a configured /dev/ttyN, and that ttyN is opened by some privileged process (optionally, a process in kernel.grsecurity.screen_lock_group), then:
- save the current tty's number to kernel.grsecurity.screen_last_tty
- switch the current tty to ttyN
- start denying authorized VT_ACTIVATE ioctls with EPERM
- start ignoring attempts to switch current tty from keyboard
- make sure the current tty is still ttyN (i.e. nothing racy happened)
- optionally, start denying sysrq (I'm not sure about this one, since I don't have it in my kernels)
Otherwise, deny the sysctl entry write with EINVAL and do nothing.
4. When the value of kernel.grsecurity.screen_lock_tty >= 0 (the screen is locked), and some number N in {-1, -2} is being written to kernel.grsecurity.screen_lock_tty, then:
- save -1 to kernel.grsecurity.screen_lock_tty
- allow authorized VT_ACTIVATE ioctls
- allow switching current tty from keyboard
- if N == -1, then try switching the current tty to the target tty with a number of kernel.grsecurity.screen_last_tty (ignore if it fails, e.g. if the target tty was deinitialized at some point)
- if N == -2, then do nothing (the assumption is the locker implementation or the user will switch tty to whatever they want)
- save -1 to kernel.grsecurity.screen_last_tty
- allow sysrq (if it was disallowed)
Otherwise, deny the sysctl entry write with EINVAL and do nothing.
5. Optional sub-features:
- configurable system-wide key combination to lock the screen (userland locker process should check for kernel.grsecurity.screen_lock_tty and see if the screen was locked by the kernel and handle it in some special way)
- simplified unlocking API: kernel unlocks the screen if the (single) process that listens on the screen_lock_tty tty exits with 0 (to be respawned by init); I'd prefer this to be optional rather than alternative
- any other suggestions?
Userland part of the locking implementation should be a privileged process that:
- has privileges to touch kernel.grsecurity.screen_lock_tty and kernel.grsecurity.screen_last_tty
- probably runs on a tty like a getty and handles the tty input (e.g. login, password) in a proper way (e.g. unlocks the screen if the login and password are correct)
- handles lock/unlock requests on a unix socket file, authenticates the peer (SO_PEERCRED) and performs any additional checks (such as whether the uid:gid is authorized to lock the screen), performs locking/unlocking
Caveats:
- Not suitable for multiseat setups. OK, there are the other locking mechanisms, so this one shouldn't be universal.
- One tty is "wasted" for the needs of the userspace part. Totally not a big deal, if you ask me.
- What if userland implementation permits screen unlocking using credentials of some user other than the one who locked the screen? Well, the userland locker process MUST track who locked the screen, i.e. what was the process credentials of the far end of the unix socket session which requested the currently active lock. Such concerns are not of the kernel part.
- What if some locker implementation fails (to unlock)? The machine remains physically locked. But there are another locking implementations, less strict ones. I think this trade-off should be acceptable if one prefers this API: more security at the cost of less convenience and fool-proof.
- What if the lock_tty is deinitialized at some point when the screen is still in the locked state? I'm not sure. Maybe the locking mechanism should be switched to the initial (unlocked) state by the kernel. Such fallback seems more like a fool-proof rather than sacrificing security.
- The rest of conditions seems more or less comparable to the existing locking mechanisms. They also can fail in case of OOM or system-wide file descriptor limit exhaustion, etc.
EDIT:
Explicitly mention deny/allow switching current tty from keyboard.