sudo-rs icon indicating copy to clipboard operation
sudo-rs copied to clipboard

Way to radically improve security of sudo: make a daemon instead of setuid binary!

Open safinaskar opened this issue 2 years ago • 1 comments
trafficstars

Manifesto. Rage against setuid binaries

I propose novel way to make sudo more secure: make it system daemon and not setuid program!

Now let me describe my idea in detail.

I think there should be no setuid binaries in the system at all. All they should be replaced by daemons. Let's pick a simple setuid binary for an example: /usr/bin/passwd (I will discuss sudo later). Passwd binary itself should be normal non-setuid program. Unprivileged user should run this program, the program should talk to special privileged passwd daemon. And that daemon should actually change password.

You may say that this will change nothing. That everything just will be same, and we just complicated everything. I disagree. The main problem with setuid binaries is that a user in large part controls environment in which setuid binary runs. Its cwd, its root directory, its environment variables, its file descriptors and many-many other things. In Linux state of current thread is described in struct task_struct here: https://github.com/torvalds/linux/blob/457391b0380335d5e9a5babdec90ac53928b23b4/include/linux/sched.h#L737 . Look how big this struct is! There was many exploits, which used this control over execution environment to break the system. Let's study some examples. But let me say first: as well as I understand, all this bugs are fixed already. But all they are based on same principles, and we clearly see that setuid binaries are very dangerous thing. Now examples:

  • Here is well-known exploit for Linux: https://lwn.net/Articles/543273/ . One of key points of this exploit is that the exploit does clone(CLONE_NEWUSER | CLONE_FS), then performs chroot and then executes setuid binary in this very unusual (i. e. chrooted) environment. And then uses that binary to gain root
  • Read this: http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ . The author combines ingenious techniques to make setuid binaries to do what he wanted. For example, he redirects stdout/stderr of a setuid binary to make it write exactly bytes he want to file he want. And moreover he runs the setuid binary in setrlimit(RLIMIT_FSIZE, environment to make sure the binary stops exactly in place he wants. Or here http://www.halfdog.net/Security/LowMemoryProgramCrashing/ the same author runs binaries (including setuid binaries) with artificial memory limits to make sure the programs will fail exactly the way he wants

All these bugs are based on same idea: run setuid binary in special environment and fool it. It is quite possible there are many-many similar yet undiscovered bugs.

Nearly all environment of setuid binary is controlled by its executor, and so setuid binary cannot trust anything. So setuid binaries should be programmed in very special style. Even runtimes (including startup code, i. e. what happens before main) of programming languages (i. e. Rust) should be carefully audited. What if they not designed with setuid in mind?

In this slides http://herpolhode.com/rob/ugly.pdf well-known early UNIX programmer Rob Pike (coauthor of UTF-8 and Go language) says in slide 22 that setuid is ugly.

You may say: if setuid is so bad, why it exists in the first place? Well, I don't know. But I think the reason is slow early computers and memory constraints. Back when UNIX was created, idea of running a special daemon instead of every setuid program would be immediately rejected, because computers simply have no that much memory.

Of course, it is not necessary to replace every setuid program with its own daemon. It is possible to combine some daemons into one. Also, it is not necessary to keep all these daemons always running. It is okay to run them using, say, socked-based activation ( http://0pointer.de/blog/projects/socket-activation.html )

Final goal is to remove all setuid programs from the system so that the whole system can be run (in case of Linux) in PR_SET_NO_NEW_PRIVS mode ( https://docs.kernel.org/userspace-api/no_new_privs.html ).

Setgid binaries and Linux fcap binaries share setuid problems, so I don't like them, too.

Now let's finally talk about how sudo should be implemented. (Of course, this is just a draft, details can change.) At system boot a privileged daemon (let's call it sudod) should be started. The daemon should somehow establish communication channel, for example, it may create socket file /run/sudo. And /usr/bin/sudo should simple not-setuid tool. When a user runs sudo foo, the sudo connects to that socket, passes necessary information, such as environment variables, stdin/stdout/stderr file descriptors (yes, you can pass file descriptors over socket). Then sudod perform all checks and starts actual program (i. e. foo) as its child. Of course, sudod performs necessary modifications of process state, i. e. changes UID, etc. Nearly all program state (i. e. task_struct) is inherited from sudod, not from sudo, and this is intentional (unless specially modified by sudod). The program starts with supplied stdin/out/err, so the user sees this program in their terminal.

Of course, special care should be taken to make sure foo feels as it is running as a child of sudo, not of sudod. For example, if the user presses Ctrl-C, sudo should catch this signal, pass it over socket to sudod, and sudod should send this signal to foo.

Until this point I gave lot of arguments why all setuid programs should be replaced by daemons. Now let me tell why specially sudo should be replaced by a daemon. The problem is that (traditional) sudo in some sense is metasetuid. Let me explain. Let's assume that someone added to (traditional) sudo's config a line, which allows everyone to call program "foo" as root. This means that starting from this moment foo became setuid-root on conceptual level. I. e. everyone now can call it as root. This means that all setuid problems described so far now applies to foo, too! I. e. malicious unprivileged user can create some special environment and run sudo foo in it. And thus authors of foo should be prepared to this. And this why I propose to write sudod daemon and run foo as a child of sudod (and inherit all process properties from sudod, except for limited few), not as a child of sudo.

Does this way have some disadvantages? Of course, it has. This will not work in simple chroot environments, now you should bind-mount /run to make sure it will work. Also, if you run sudo in some container, you now need to make sure that sudod is running in this container. But I still think that my proposal is better than current situation despite this problems.

Setuid is UNIX design bug. Probably caused by small RAM of early machines. Let's fix it

safinaskar avatar May 11 '23 15:05 safinaskar

I love this proposal because it makes me think about a possibility I hadn't considered before. But just some quick first impressions:

The proposal is reminiscent of microkernel architecture; the responsibility for attesting the call posture moves to a resident user space daemon, instead of a monolithic kernel. Do you get enough of the security benefit without eliminating the kernel functionality though? if there are other suid binaries left on systems, do we just end up with more risk in practice? Now the kernel can be targeted, but so can the sudo daemon.

Secondly, how challenging does it become to glue together session state? Today sudo invocations and their children are automatically and intrinsically tied to a pty session and a hierarchical process family tree ... which matters for things like quota enforcement, auditd, bpf traces, etc. If that state ends up having to be ported into the sudo daemon, is that a net win? That could end up being a fair amount of complexity being maintained in two places.

colmmacc avatar May 11 '23 15:05 colmmacc

@colmmacc

if there are other suid binaries left on systems, do we just end up with more risk in practice?

There are exploits, which need at least one (any) setuid binary, such as mentioned https://lwn.net/Articles/543273/ . So, from a point of view of this exploit removing one setuid binary changes nothing. But there are other exploits, which use particular setuid binaries. And so removing any one setuid binary is a win

Now the kernel can be targeted, but so can the sudo daemon

Attack surface of a daemon is smaller than of a setuid binary. There is only one thing that user controls when they talk to a daemon: data passed through socket. If we have setuid binary, user controls large part of environment, which the binary runs in.

But yes, other setuid programs still can be targeted and possibility of exploits affecting any setuid binary (such as https://lwn.net/Articles/543273/ ) remains.

Secondly, how challenging does it become to glue together session state? Today sudo invocations and their children are automatically and intrinsically tied to a pty session and a hierarchical process family tree ... which matters for things like quota enforcement, auditd, bpf traces, etc

I don't know. This is just idea. :) But keep in mind that docker command starts containers as children of system daemon, not as the command itself. So somehow docker fixed this problems, and so we can, too. :) Or some problems left unsolved by docker, but as you can see, this doesn't prevent people from using it. :)

On pty: as well as I understand, when I start docker container, pty is allocated for it. And then all data is transferred between this pty and actual user's terminal (similarly to ssh). We can do the same.

Also: one of the reasons for creating podman is very this docker feature. I. e. in podman container can be started as a child of podman command, and thus quotas can be enforced. But, as you can see, not everyone switched to podman. Zillions of people still use docker, and they are happy. :)

quota enforcement

See http://www.halfdog.net/Security/LowMemoryProgramCrashing/ . And see again example from http://www.halfdog.net/Security/2015/SetgidDirectoryPrivilegeEscalation/ . The author calls setrlimit(RLIMIT_FSIZE, ...) and thus limits data written by setuid program exactly as he need. To trick setuid program into creating exact correct ELF binary he wants. So, inability to create arbitrary quotas/limits is feature, not bug. But it is possible that we will add to sudo possibility of enforcing quotas. But in controlled manner, to prevent this problems from halfdog.net

safinaskar avatar May 11 '23 19:05 safinaskar

sorry but this discussion goes beyond the scope of this project which is intended to be a sudo replacement

pvdrz avatar May 12 '23 07:05 pvdrz

Manifesto. Rage against setuid binaries I think there should be no setuid binaries in the system at all. All they should be replaced by daemons. Let's pick a simple setuid binary for an example: /usr/bin/passwd (I will discuss sudo later). Passwd binary itself should be normal non-setuid program. Unprivileged user should run this program, the program should talk to special privileged passwd daemon. And that daemon should actually change password.

I have good news, this already exists in Linux as systemd-homed and homectl.

dptpirate avatar May 12 '23 15:05 dptpirate

Similar idea is used in dbus and privileged operations.

benner avatar May 13 '23 19:05 benner