Skip to content

greenm01/lockme

Repository files navigation

lockme

A hardened, minimalist screen locker for Wayland compositors that implement ext-session-lock-v1.

Matrix rain lock screen

lockme aims at one thing: keep a typed password out of every place the kernel and userspace would otherwise let it leak.

The password buffer is page-aligned, mlock'd, marked MADV_DONTDUMP, and wiped with explicit_bzero on every clear. The locker process drops dumpability, denies new privileges, and suppresses core dumps. PAM runs in a forked child that talks back over a length-prefixed pipe, so a misbehaving auth module never shares an address space with your secret.

The UI defaults to GPU-rendered Matrix rain while idle. Typing switches to a solid configurable palette, failed auth shows a solid failure color, and Alt-B toggles between Matrix and a blank screen. Colors and the rest of the runtime knobs live in a small KDL 2.0 config file (see Configuration).

It is small on disk too: a stripped release binary is 558 KiB, well under the size of hyprlock and several times smaller than waylock while shipping more hardening than either. See compare.md for the full security and size comparison against swaylock, waylock, and hyprlock.

Press Alt-B to toggle between a blank screen and the matrix rainfall.

Why

I was missing waylock on the Niri window manager and decided to write a new one from scratch — and because I dig Nim.

Install build dependencies

lockme needs Nim/Nimble, a C toolchain, pkg-config, the Nimble package nimkdl >= 2.1.0, and development headers for Wayland, Wayland EGL, EGL, OpenGL ES 3, xkbcommon, and PAM. The OpenGL ES link dependency is discovered through the standard glesv2 pkg-config module, even though the renderer includes GLES3 headers.

Void Linux:

sudo xbps-install -Sy nim base-devel pkg-config wayland-devel libglvnd-devel libxkbcommon-devel pam-devel
nimble install -y nimkdl

Void, Arch Linux, Debian, and Ubuntu package nimble with nim.

Arch Linux:

sudo pacman -S --needed nim base-devel wayland libglvnd libxkbcommon pam pkgconf
nimble install -y nimkdl

Debian/Ubuntu:

sudo apt update
sudo apt install nim build-essential libwayland-dev libegl-dev libgles-dev libxkbcommon-dev libpam0g-dev pkg-config
nimble install -y nimkdl

Build

Build dependencies:

  • Nim 2.2.0 or newer
  • Nimble package nimkdl >= 2.1.0
  • a C compiler
  • pkg-config
  • development packages for wayland-client, xkbcommon, pam, egl, glesv2, and wayland-egl
  • OpenGL ES 3 headers, normally provided by the same development package that provides glesv2

Protocol refresh dependency:

  • wayland-scanner
nimble build

Release builds use checked-in Wayland protocol stubs, so wayland-scanner and wayland-protocols are not required unless you are refreshing those generated files.

Format

Formatting uses nph:

nimble setupTools
nimble fmt
nimble fmtCheck

nimble setupTools installs the pinned formatter as a user-level Nimble tool. nimble fmt formats maintained Nim sources. nimble fmtCheck is the non-mutating check form for review or CI. The generated Matrix font source is left to nimble regenFont.

Deploy

nimble deploy

This builds an optimized release, installs lockme to ~/.local/bin/lockme, and installs the PAM service file to /etc/pam.d/lockme. The binary install runs as your user; the PAM install uses sudo because /etc/pam.d is root-owned.

Check compositor compatibility

lockme --check-protocols

At runtime, the compositor must advertise:

  • ext_session_lock_manager_v1
  • wp_viewporter
  • either wl_shm or wp_single_pixel_buffer_manager_v1

wl_shm is used for the default color surfaces when available. wp_single_pixel_buffer_manager_v1 is optional and is used for solid-color buffers when available.

Wayland protocol sources

lockme uses libwayland-client directly through a small C shim and generated protocol stubs. It does not depend on a third-party Wayland wrapper library; this keeps the C/Nim boundary explicit and leaves protocol handling on the standard Wayland C stack.

The generated protocol files are checked in under src/lockme/protocols. Their XML sources are vendored in src/lockme/protocols/xml:

  • ext-session-lock-v1 from wayland-protocols/staging
  • single-pixel-buffer-v1 from wayland-protocols/staging
  • viewporter from wayland-protocols/stable
  • xdg-shell from wayland-protocols/stable

To refresh the generated C/header files after updating the XML:

nimble regenProtocols

Refreshing protocols requires wayland-scanner. The task regenerates C/H from the vendored XML only; update the XML from wayland-protocols first when intentionally moving to a newer protocol revision. Commit the XML and generated C/H changes together.

Run

lockme

Plain lockme shows Matrix rain while idle and ignores the Enter key on an empty password buffer.

Battery / power

If you use a laptop, set --idle-timeout. A continuously rendering GPU screensaver prevents deep sleep and drains the battery noticeably on an unattended locked machine. Setting an idle timeout blanks the screen after the specified number of seconds, letting the system reach low-power states:

lockme --idle-timeout 60   # blank after 60 s of inactivity

The next keypress wakes the screen back to matrix. You can also set this in ~/.config/lockme/config.kdl so you never forget it:

idle-timeout 60

Other common flags

--blank starts on a blank screen instead of matrix rain. --allow-empty-password allows an empty Enter press to reach PAM. --no-gpu forces the CPU renderer and removes EGL from the process (also useful on systems with unreliable GPU drivers).

For development only, lockme --dev-mode makes Esc unlock and exit cleanly without talking to PAM. This is intentionally insecure and should not be used for a real screen lock, but it provides a compositor-safe escape hatch while testing lockme itself.

For screenshots while developing the Matrix renderer, use:

lockme --dev-mode --dev-window

This opens the Matrix rain in a normal Wayland window and does not lock the session or start PAM.

Matrix rain is rendered through a Sokol/EGL/GLES path when available. The glyph atlas is generated from lockme's built-in high-resolution alpha glyph source, which is rasterized from the CNTR Koine Greek TrueType font; if GPU setup fails, lockme warns and falls back to the existing software renderer. The GPU rain pipeline adapts MIT-licensed shader logic from Rezmason's Matrix rain renderer. While locked, Alt-B toggles between Matrix rain and a blank screen.

Typing rotates the surface through a configurable input palette, and failed authentication shows a solid failure color. The --init-color, --input-color (repeatable), and --fail-color flags override the defaults.

Default palette

State Color RGB
At rest (init) Black 0x000000
Typing (Father) Indigo 0x4B0082
Typing (Son) Royal Blue 0x003366
Typing (Spirit) Life Green 0x006400
Auth failure Crimson 0x8B0000

Repeating --input-color defines a custom palette. The first occurrence replaces the built-in palette; later occurrences append:

lockme --input-color 0x111111 --input-color 0x222222 --input-color 0x333333

Configuration

lockme reads an optional KDL 2.0 configuration file. The discovery order is:

  1. --config <path> (must exist if specified),
  2. $XDG_CONFIG_HOME/lockme/config.kdl (default ~/.config/lockme/config.kdl),
  3. each $XDG_CONFIG_DIRS/lockme/config.kdl in order (default /etc/xdg).

--no-config disables the search entirely. CLI flags always win over values set in the config file. Parse and validation errors abort startup with a diagnostic on stderr.

A documented template lives at examples/config.kdl and is dropped into ~/.config/lockme/config.kdl by nimble installBin/nimble deploy only if that file does not already exist. Keys absent from the config always take the built-in default, so an existing config never breaks when new options are added; diff your file against examples/config.kdl after pulling updates to see what is new. Both 0xRRGGBB and #RRGGBB color forms are accepted in the config file (the CLI requires the 0x form). Legacy Matrix font keys from older templates are accepted as no-ops; Matrix glyphs now always come from the built-in Koine Greek glyph source. By default, Matrix glyph size follows the upstream Matrix renderer's 80-column grid. Set matrix-cell-scale "auto" in the config for responsive sizing, or set it to a number from 1.0 through 8.0 only if you want a fixed glyph scale.

The built-in Matrix glyph alpha data is generated from the vendored CNTR KoineGreek.ttf font in third_party/cntr-font, copyright 2012-2023 Alan Bunning / Center for New Testament Restoration and distributed under CC BY-SA 4.0. Run nimble regenFont after changing the vendored font or glyph list; the generator uses Python with Pillow and fontTools.

Build size

The release build uses size-oriented flags (--opt:size --mm:orc -d:useMalloc -flto -Wl,--gc-sections -Wl,-s) so that the KDL parser and its transitive dependencies (bigints, unicodedb) do not bloat the binary. The current stripped output is 558 KiB (570,808 bytes). Run nimble sizecheck to print the size of your build.

Platform requirements

lockme is Linux-only. It relies on the following Linux-specific facilities to harden the password buffer and the auth child:

  • mlock(2) and madvise(MADV_DONTDUMP) on a page-aligned password buffer, preventing it from being paged to swap or included in core dumps.
  • Best-effort mlockall(2) on the locker process and auth child to keep transient password material on the stack and in libc/PAM internals out of swap when RLIMIT_MEMLOCK permits it. Matrix mode and the auth child use MCL_CURRENT; --blank uses MCL_CURRENT | MCL_FUTURE in the locker process.
  • explicit_bzero(3) (glibc/musl) for password clearing that the compiler is not permitted to elide.
  • prctl(PR_SET_DUMPABLE, 0) on both the parent and the auth child to block ptrace and /proc snooping by other processes of the same UID.
  • prctl(PR_SET_NO_NEW_PRIVS, 1) on the parent after the PAM auth child is forked, so future parent-side execve cannot gain privileges without breaking PAM helpers such as unix_chkpwd.
  • setrlimit(RLIMIT_CORE, 0) to suppress core dumps for the locker.
  • close_range(2) (kernel 5.9+) in the auth child to drop inherited file descriptors before invoking PAM; falls back to a manual loop on older kernels.

Security

lockme mirrors waylock's privilege-separation model: the parent process holds the Wayland connection and renders the lock surface, while a forked child performs PAM authentication over a length-prefixed pipe. The password buffer:

  • has a fixed 1024-byte capacity rounded up to a page,
  • is allocated via posix_memalign and mlock'd for its lifetime,
  • is excluded from core dumps via madvise(MADV_DONTDUMP),
  • is zeroed via explicit_bzero on every clear (including after each failed authentication and after each Backspace),
  • has its protections re-applied after --fork-on-lock.

The auth child also re-applies best-effort mlockall(MCL_CURRENT) before initializing PAM, because memory locks are not inherited across fork(2).

With --fork-on-lock, the background process additionally redirects stdin/stdout/stderr to /dev/null to avoid SIGPIPE if the parent shell is closed.

RLIMIT_MEMLOCK must be at least the password buffer size (one page). Process-wide mlockall needs more headroom and can fail under normal desktop mappings or restrictive limits. That best-effort failure is reported only at --log-level debug; the locker continues with the password buffer's own mandatory mlock still active.

See audit.md for the running security and performance review log, including the recent PAM, Matrix renderer, signal handling, and SHM sizing checks.

PAM stack

lockme performs authentication through PAM. The shipped default pam.d/lockme is a minimal, auditable, distribution-independent chain:

auth        optional      pam_faildelay.so delay=2000000
auth        required      pam_unix.so     nullok
account     required      pam_unix.so

This verifies a plain Unix password and applies a two-second failure delay without recording faillock tallies or locking the user out after mistyped passwords. That avoids the bad screen-locker failure mode where three incorrect attempts can block a later correct password for several minutes. Most Linux users authenticate this way and gain nothing from a larger PAM stack on their screen locker, so this is the default.

The default does NOT enable pam_systemd_home, GNOME Keyring or KWallet auto-unlock, fingerprint readers, smartcards, or any other auxiliary auth method. If you need any of those, install the full PAM file instead:

nimble installPamFull
# or, without nimble:
sudo install -m 0644 pam.d/lockme.full /etc/pam.d/lockme

The full file contains a single line, auth include system-auth, which delegates authentication to the distribution's system-auth chain. This is the same approach waylock and most other screen lockers ship with. The trade-off is that lockme's effective auth surface becomes whatever system-auth says it is. To audit it, read /etc/pam.d/system-auth; edits there (for example a debugging auth sufficient pam_permit.so line, or a pam_succeed_if clause that bypasses checks for a group) silently affect lockme as well, and lockme cannot defend against this.

For PAM debugging, run ./lockme --log-level debug --blank --dev-mode from a terminal. Debug logging records PAM status codes and messages only; it does not log password contents, password length, or prompts. In --dev-mode, Esc exits without asking PAM, which keeps manual auth tests recoverable.

To revert to the default minimal chain at any time:

nimble installPam

About

A secure Matrix style GPU rendered screen-lock for Wayland.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages