systemd wants to replace sudo. I hate that they're not wrong about why.
Published by RodHat

run0 has been shipping in systemd 256 since mid-2024. It’s Lennart Poettering’s
privilege escalation primitive: instead of a SUID binary that caches credentials and
answers “are you still root from 15 minutes ago,” run0 uses systemd-run to spawn
a fresh, tracked session as the target user. No credential cache. No SUID bit. Every
escalation is a new session with its own cgroup slice.
The news this week is that a couple of major Linux distributions are now defaulting to
it or shipping it as the recommended path in their tooling guides. Not replacing the
sudo package outright — yet — but nudging toward it hard enough that this is no longer
a thing that lives in blog posts.
I have thoughts.
Let me get the scope-creep complaint out of the way
systemd owns your init, your logging, your login sessions, your DNS resolver, your network management, your boot loader on some configurations, your NTP client, and now your privilege escalation model. I have not lost my mind; this is, in fact, one process hierarchy to rule them all.
The counterargument — “sudo is already a giant C binary with its own plugin architecture,
its own config language, and its own CVE history, what exactly were you defending” — is
true, but that’s not the point. The point is that each individual piece of systemd has a
reasonable defense and the whole thing adds up to a system you cannot reason about in
isolation. You can’t ktrace your way to a complete picture of what a modern systemd
machine is doing in the same way you could when it was sysvinit plus a handful of daemons
you’d actually read. The jail isolation model has the same problem at a different layer: the
boundary is only real if every piece of it is where the diagram says it is.
Complaint registered. Moving on, because I try to keep up with the policy of being honest about what’s actually true.
What sudo’s security model actually is
sudo is a SUID root binary. When you run sudo thing, the kernel sees the SUID bit,
elevates to uid 0, and hands control to the sudo executable which then decides whether
you’re allowed. The security guarantee lives in that binary’s correctness — any exploitable
bug in sudo’s parsing, its plugin interface, its environment handling, or its config
grammar is a local privilege escalation. The SUID bit makes that a kernel-enforced
guarantee, which is good, but it also means the entire attack surface is one binary with
root in its pocket.
The credential cache is a separate problem. By default, sudo remembers that you
authenticated for 15 minutes. This is a gift to anyone who can interact with your terminal
session during those 15 minutes, including scripts that ran from a working directory you
don’t control, processes you launched that you thought were safe, or the other user on
the machine who has access to your tty. It’s a convenience feature that reduces your actual
security margin every time you use it.
What run0 does differently
No SUID binary. run0 wraps systemd-run --pty --wait --collect --service-type=exec --unit=... --uid=root, which asks the running systemd instance — which is already root’s
process — to spawn a session. The PAM authentication happens in a purpose-built transient
service unit, isolated in its own cgroup, with its own PTY. When the session ends, the
cgroup is gone. There is no 15-minute window where the credential is valid. Every
escalation is a discrete, logged, accounted event in the journal, attached to your login
session in the systemd session tracking.
The session accountability alone is worth something. sudo logs to syslog or a file;
run0 sessions are journal entries tied to your user session’s cgroup, which means they
survive in the same structured log that everything else on the system is writing to, with
the same structured fields you’d query with journalctl. Whether you trust that enough
depends on how much you trust systemd’s journal integrity, which is a separate argument.
OpenBSD solved this in 2015 with fewer lines of C
doas — a 340-line reimplementation of the subset of
sudo that people actually use — has been the default on OpenBSD since 6.0. It doesn’t
cache credentials. Configuration is one file with one dead-simple grammar. No plugins, no
LDAP integration, no per-command environment manipulation. The security surface is small
enough that you can actually read the whole source.
FreeBSD has had doas as an optional port for years. Nobody in the Linux world reached
for it, because Linux distros have a long tradition of solving problems by adopting whatever
the largest adjacent project ships, and sudo is old and universal and everyone knows it.
The correct answer to “sudo’s security model is subtly broken” was doas. The answer we
got was run0. Both address the real problem. One of them doesn’t require you to live
inside systemd’s session graph to use it.
The reluctant verdict
run0’s security model is sounder than sudo’s. No credential caching, no SUID binary
attack surface, discrete session accountability. These are real improvements, not cosmetic
ones. The same logic that makes unprivileged eBPF a bad default applies
here: a design that makes CVEs easier to exploit is a design problem, not a CVE problem,
and sudo’s SUID-plus-cache model has been making local privilege escalations easier than
they need to be for thirty years.
I still think this was the wrong vehicle. doas is a better answer to the same problem —
smaller, auditable, composable, doesn’t require buying into an ecosystem. But if you’re
already fully on systemd and the question is “sudo or run0,” you should probably be running
run0. The part that was wrong with sudo wasn’t the privilege escalation; it was the
credential caching and the SUID surface, and both of those are gone.
You’re allowed to be annoyed about who shipped the better answer while still using the better answer. That’s not hypocrisy. That’s just having standards and living in the real world at the same time.
If you want to understand what the SUID bit actually does in the kernel — why it’s an effective but blunt instrument, and what the exec path looks like when the kernel sees it — The Linux Programming Interface has the definitive treatment of credentials, process privileges, and the exec model. Chapter 9 on process credentials and Chapter 38 on writing secure programs are the relevant sections. For the OpenSSH parallel of “we’re removing the thing that made the old design load-bearing,” the legacy algorithm removal story is the same arc: the old design wasn’t wrong for its time, and the new default is better, and you’re going to be annoyed about updating your configs anyway.