Skip to content
Build 03: Operating system

A privilege-separated display manager.

The graphical login for a Linux system, split across a root daemon and an unprivileged interface that talk over a documented protocol. Written in Rust, for a runit and elogind system, as the login layer of the operating system work.

RustPAMX11runitelogindD-Bus
01: The problem class

The program that holds root and owns the screen.

A display manager sits at an unusual intersection. It authenticates, so it handles credentials. It starts sessions, so it holds root. It draws an interface, so it parses input from whoever is at the keyboard, including whoever should not be. Most designs in this category put all three in one process, which means an interface bug and a root compromise are the same event.

It is also the layer with nothing beneath it to blame. When a display manager fails it usually fails holding the display, so the diagnostic surface a normal program would rely on is the thing that just broke. A user is left at a black screen on their only computer, with no visible way back.

That inverts the usual priorities. The privilege boundary and the recovery path are not hardening to add once it works; they are the design, and everything else is arranged around them.

02: What it demanded

The requirements that came out of that.

Every one is about limiting what a failure can reach, because at this layer a failure reaches the whole machine.

  • A privilege boundary that a session bug cannot reach back through
  • Credentials that do not outlive the moment they are used
  • No path where a compromised interface can start a session
  • Recovery from a crash that does not depend on the screen still working
03: The decisions

How the authority was split.

Four calls that define the security model. Each has a simpler opposite, and the simpler opposite is what most of the category does.

Stated as decisions rather than features, because each cost something real. What follows is which way this one went and what it bought.

  1. Three processes, not one

    A root daemon starts a per-login worker that owns the whole authentication lifecycle, and that worker forks the session child which irreversibly drops privilege before running anything of the user's. The process holding the authentication handle never becomes the user, and the process that becomes the user never holds it. A single-process design is far simpler and makes any bug in it a root bug.

  2. The interface is unprivileged and replaceable

    The greeter runs as its own unprivileged account and speaks to the daemon over a versioned unix-socket protocol with a length-prefixed frame, a size cap and a version handshake. It cannot start an unauthenticated session and cannot pass a free-form command across the boundary: it may only name a session the system itself discovered. Publishing that protocol under a permissive licence is what lets somebody write a different interface without inheriting the daemon's licence.

  3. Single-threaded and blocking, deliberately

    No async runtime, no thread pool. A process that forks must not have other threads alive when it does, and the entire model depends on forking correctly at the exact moment privilege changes. Giving up concurrency here buys the one guarantee the security model cannot do without.

  4. A black screen is treated as a defect, not an accident

    Terminal state is restored from signal handlers using only calls that are safe to make from one, rather than from ordinary cleanup code that a hard crash never reaches, with a service hook and a check on the next boot behind it. Text consoles stay available as a way back in. It is the recovery path for the case where the thing that failed is the thing you would have used to diagnose it.

04: The trade

The decision that mattered.

One call per build, taken with a real alternative on the table. The alternative is named here, and so is what choosing against it cost.

Three processes where one would have worked.

Authentication, privilege and the interface are split across separate processes that talk over a defined protocol, rather than living together in one program the way most of this category does.

It is the decision everything else on the page follows from. Once the interface cannot reach the authority, the remaining questions are about how the pieces talk, which is engineering. Left in one process, no amount of care in the rest recovers the property.

The alternative
One process, running as root, doing all three. It is the shape of the tool this one is replacing and it is genuinely simpler: no protocol to define, no version handshake, no serialisation, no partial failure between components, and a session lifecycle you can read top to bottom in one file.
What it cost
A large amount of complexity, permanently, for a program whose whole job is to show a password box. A wire format with a version negotiation, a socket whose peer has to be verified, forking at exactly the right moments, and a set of failure modes that only exist because the pieces are separate. All of it is code that can be wrong, in a program where being wrong is expensive.
Why the trade holds
In a single process, a bug in the part that parses keyboard input is a bug in the part that holds root, because they are the same address space. That is not a hypothetical risk profile, it is an identity. The split converts the worst case from full machine compromise into a crashed interface that could not have started a session anyway. Complexity is a price that can be paid once and reviewed; the single-process design charges its price only once, and never refunds it.
No published measurement

No boot figure, no memory figure, no audit result. The security argument above is structural, about what a failure can reach, and nothing here has been independently reviewed or formally verified. That absence is stated rather than papered over: this is pre-v1 software making a design argument, not a program with a security record.

03: Next step

Ask about any of these.

The specifics are not on this page. Ask for them directly, or send the problem you actually need solved.