Here's the 5th installment of my series of posts highlighting key new features of the upcoming v256 release of systemd.
I am pretty sure all of you are well aware of the venerable "sudo" tool that is a key component of most Linux distributions since a long time. At the surface it's a tool that allows an unprivileged user to acquire privileges temporarily, from within their existing login sessions, for just one command, or maybe for a subshell.
"sudo" is very very useful, as it…
… allows users to operate at minimum privilege: do most of their work without privileges but temporarily acquire them where needed, all without leaving the shell workflow, integratable with shell scripts, pipelines and so on.
sudo has serious problems though. It's a relatively large SUID binary, i.e. privileged code that unprivileged users can invoke from their own context. It has a complicating configuration language, loadable plugins (ldap!), hostname matches and so on and so on.
This has led various people to revisit the problem and come up with alternatives: most prominently there's probably OpenBSD's sudo replacement called "doas". While it greatly simplifies the tool and removes much of the attack surface, it doesn't change one key thing: it's still a SUID binary.
I personally think that the biggest problem with sudo is the fact it's a SUID binary though – the big attack surface, the plugins, network access and so on that come after it it just make the key problem…
… worse, but are not in themselves the main issue with sudo.
SUID processes are weird concepts: they are invoked by unprivileged code and inherit the execution context intended for and controlled by unprivileged code. By execution context I mean the myriad of properties that a process has on Linux these days, from environment variables, process scheduling properties, cgroup assignments, security contexts, file descriptors passed, and so on and so on. A few of these settings the kernel is nice…
… enough to clean up automatically when a SUID binary is invoked, but much of it has to be cleaned up by the invoked suid binary. This has to be done very very carefully, and history has shown that SUID binaries are generally pretty shit at that.
So, in my ideal world, we'd have an OS entirely without SUID. Let's throw out the concept of SUID on the dump of UNIX' bad ideas. An execution context for privileged code that is half under the control of unprivileged code and that needs careful, …
… manual clean-up is just not how security engineering should be done in 2024 anymore.
With systemd v256 we are going one step towards this. There's a new tool in systemd, called "run0". Or actually, it's not a new tool, it's actually the long existing tool "systemd-run", but when invoked under the "run0" name (via a symlink) it behaves a lot like a sudo clone. But with one key difference: it's *not* in fact SUID. Instead it just asks the service manager to invoke a command or shell under…
… the target user's UID. It allocates a new PTY for that, and then shovels data back and forth from the originating TTY and this PTY.
Or in other words: the target command is invoked in an isolated exec context, freshly forked off PID 1, without inheriting any context from the client (well, admittedly, we *do* propagate $TERM, but that's an explicit exception, i.e. allowlist rather than denylist).
One could say, "run0" is closer to behaviour of "ssh" than to "sudo", in many ways. Except that…
it doesn't bother with encryption or cryptographic authentication, key management and stuff, but instead relies on the kernel's local identification mechanisms.
run0 doesn't implement a configuration language of its own btw (i.e. no equivalent of /etc/sudoers). Instead, it just uses polkit for that, i.e. how we these days usually let unpriv local clients be authorized by priv servers.
By isolating the contexts and the resources of client and target we remove some other classes of attacks…
… entirely, for example this stuff:
https://ruderich.org/simon/notes/su-sudo-from-root-tty-hijacking
But enough about all that security blabla. The tool is also a lot more fun to use than sudo. For example, by default it will tint your terminal background in a reddish tone while you are operating with elevated privileges. That is supposed to act as a friendly reminder that you haven't given up the privileges yet, and marks the output of all commands that ran with privileges appropriately. (If you don't like this, …
… you can easily turn it off via the --background= switch). It also inserts a red dot (unicode ftw) in the window title while you operate with privileges, and drops it afterwards.
And since it's just systemd-run called under a different name it supports the --property= switch that systemd-run supports, i.e. it allows you to set arbitrary service settings for the invoked privileged command/session if you like.
Anyway, that's all for now. Enjoy "run0"!
@pid_eins When you say "while you are operating with elevated privileges", do you mean you can use run0 to create a root shell to work in, or do you just mean while the current programm I started as run0 ist running?
@stephan not sure I grok what you are saying. run0 can invoke a shell as root for you, but can also invoke any other command you like as root. The shell is after all just a program too. A program that is typically used to invoke further programs, but still just a program.
Regardless what you precisely invoke: run0 will tint the bg of its output (and your input) in a reddish tone. Once the program terminates this tinting ends, and you get your usual black (or white or whatever) background back.
@pid_eins Yes that explains it. I kinda forget that running a shell is also just running a programm. Looking forward to run0.
@pid_eins wow the auto coloring and title changes would be really annoying. The config to disable it seems to be not user friendly, too:
--background=switch
Whatever background means in this case and why it’s correct to set to switch seems like arbitrary to me.
The tool itself seems to be indeed great!
@amanzer @sszuecs oh. i didn't grok @sszuecs's comment, I was sure there was some misunderstanding but I couldn't figure out how! But your explanation explains the misunderstanding perfectly! So yes, the switch is called "--background=" and you can override the background color with that, or you can assign an empty string to disable the tinting altogether.
@pid_eins, like https://gist.github.com/JBlond/2fea43a3049b38287e5e9cefc87b2124/da3a76d981e3ee95d00f14db07975e088a02a311#regular-colors? Why is that preferable to hex codes with alpha channels and all that goodness?
@rokejulianlockhart because this is about terminal output, and terminals eat ansi sequences and have no concept of alpha channels?
@reto @rokejulianlockhart the switch accepts 256 and full rgb ansi colors too. But still, there is no alpha channel concept in ansi sequences.
@pid_eins, thanks. Is this due to an outdated standard, or mere convention?
@pid_eins I think it's a good take one sudo. I think there were some vulnerabilities found in sudo recently, so it's good to look at other ways to do it.
I wonder what the adoption of this tool is gonna be. Just very pragmatically, the name run0 is harder to type than sudo. So you would have to make a shell alias. Would it be possible for a distro to completely replace sudo with this and create an alias by default? Maybe it would need to get a compatibility mode for it to replace sudo
@TheStroyer the command line of run0 is intentionally kept close to sudo's. But that's were the compatibility really ends, i.e. /etc/sudoers and so on we're never going to add compat for.
From my perspective run0 should be fine already for a distro to replace sudo with. But let's see how this plays out, I am pretty sure there might be a feature or two we still need to add before the first distros decide it's ready to switch over.
And I am pretty sure there are plenty of distributions…
@TheStroyer … which think that things like pluggable client-side modules, LDAP and so on are actually a good thing, even though I would vehemently disagree with that.
@pid_eins Same disagreement here. But also iirc policykit is extensible right? So if someone is brave enough they could probably extend pk with whatever things they want, and then it can apply to all privilege escalation not just run0!
Maybe I shouldn't give people ideas...
@danderson PK is scriptable with JS, so people kinda can do any kind of shit with it, of course. But I think it's much less problematic than what sudo is doing, because PK runs that stuff in a well defined execution context forked off PK's service which runs unprivileged – and not on the client side, in an icky, undefined, half-inherited mess of an execution context under user control – like sudo does it.
@pid_eins 100% yes. N:M dlopen() in unpredictable contexts should really go away as a plugin mechanism, it's so easy to create massive problems.
I saw some distros try to fix PAM this way too, they (ab)use the nscd caching protocol in glibc to move all lookups into a system daemon, and hide the .so's from the rest of the system. Same idea, move all the scary plugin stuff to a central location where you can manage it more safely.
@pid_eins Looks like there was a short discussion on the glibc ML in 2022 about formalizing this interface and explicitly support the privilege separation use case, but I'm not sure if anything more happened since...
@danderson from my perspective as a systemd person I wouldn't ask for that from glibc. We have nss-systemd these days which is an IPC frontend to the generic varlink IPC API described here:
https://systemd.io/USER_GROUP_API
Anyone can plug in their stuff there. It's generic, extensible and IPC based. Unless you are a systemd hater I see no reason why we'd need to shove that into glibc.
@pid_eins Ooh, TIL! I didn't know about this entire subsystem of systemd. My worry was going to be what to do with the cursed legacy modules that some people have, but I see with userdbd there's bidirectional compatibility between varlink and nss clients. Very nice, and yes I agree this is a better way to set up the system.
I look forward to the posts for next release, I'm even learning stuff about older releases!
@danderson @pid_eins if later systemd is not enough sssd has provided this service with proper caching for almost 15 years.
It was built specifically for 3 reasons: consistent caching, remove unwanted libraries from binaries, allow centally controlled private credentials.
Because it predates systemd it has its own protcol but also a fast, mmaped shared cache that avoid process context switching when not needed.
(full disclosure I am the original author).
@pid_eins So the intention for it to be a replacement for sudo right? I.e. sudo does not need to be present on a system.
@TheStroyer yes, definitely.
@pid_eins Could you expand on the LDAP thing?
Are you saying LDAP is a bad thing in general or how authentication works with it?
@leo Also the first time for me hearing about that!
@tsrberry sudo has a dlopen() based plugin interface. It is used to use LDAP directories as source for sudo rules. (I think that's pretty much it's only use.)
For this, highly privileged sudo, running in an inherited execution context controlled by an unpriv user is doing complex network protocol parsing. What would possibly go wrong?
@pid_eins I had no idea about this plugin interface, that is crazy.
Thank you!
@pid_eins I agree with the general thrust of your argument, but Polkit running JavaScript in a privileged context doesn't seem like much of an improvement on the configuration front. Or maybe I'm misunderstanding the architecture of Polkit here.
@josh @TheStroyer but there are also plenty which never should be copied over. Hence adding a blanket copy switch for all env vars sounds highly problematic to me.
@pid_eins @josh @TheStroyer maybe some out of band protocol for accessing the "caller context" for programs that are specifically designed to be run0'd?
@josh @TheStroyer @pid_eins if I'm not wrong, aren't EDITOR and PAGER paths to binaries? sounds potentially dangerous. Maybe an allowlist?
@josh yes but presumably the person with root-equivalence knows *which* binary they're running. Maybe it's even included in some non-forgeable credential prompt (ha ha ha).
They may *not* know - or know the significance of - every environment variable, and other bit of ambient context (which seems to be the part of the point here?)
@pid_eins @TheStroyer If a distro ships this and is able to stop depending on SUID entirely, could we have system-wide PR_SET_NO_NEW_PRIVS?
@muvlon @TheStroyer there's a knob for enabling system-wide nnp in systemd's system.conf. but for a general purpose distro its probably too early to enable. But i think getting there would be a very worthy goal.
@pid_eins How would one check, within a bash script, if it was executed with run0?
Verifying the same for/with sudo always felt confusing to me, with countless different ways to do it, each one less readable than the previous..
@verbumfeit sudo sets a bunch of env vars, and we set the same (i.e. SUDO_UID/SUDO_GID/SUDO_USER), for compat.
@pid_eins yesss this is great, thank you. it would be really nice if NO_NEW_PRIVS was more widely used, and I think daemon-based sudo is a big step towards that...
@pid_eins It sounds like a great improvement on sudo, but I have a question: how do you pronounce "run0"?
@smlx maybe runzero?