Plasma GitLab Archive
Projects Blog Knowledge

KNOWLEDGE BASE ON CAMLCITY.ORG: linux

KB 003: Disable setuid-root on Linux

What I learned when programming the GODI autobuilder - by Gerd Stolpmann, 2012-12-30

In Linux books and the usual documentation you can read about two ways of securing chroot environments: by clearing the setuid-root bits file by file, or by disabling the setuid mechanism in the mount flags. Actually, there is a third way since kernel 2.6.26.
Recently I secured the sandbox environment running the GODI autobuilder. It compiles and possibly runs untrusted code coming from the GODI repository. One open security hole was that this chroot environment did not systematically disable setuid. As you might know, it is possible on all Unix OS to set a flag for an executable so that this program runs as a different user (setuid bit). If this other user is root, the executable gains system privileges, and this can be potentially abused to run user code with such privileges. Well, not every setuid program is a problem, but it might be (generally or in special circumstances), and it is good practice to turn off all setuid execution in secured chroot environments.

My chroot directories are partially bind-mounted, and thus I could not easily disable setuid in the mount flags. But there is another way: Just clear the capability bounding set (bset), and you are done.

Capabilities

What's that? Well, the root user gets under Linux its special privileges because it has capabilities. Every capability corresponds to a special right, e.g. root can run "chown" with arbitrary arguments because it has the CAP_CHOWN bit set. A non-root user has no capabilities. So our goal is to prevent that root (or any other user) can get capablities in the secured environment. When an executable is run setuid-root the kernel just adds the capabilities of root to the process (besides changing the uid to 0).

The trick is now that one can restrict the capabilities a process (and all its subprocesses) can ever get. There is a mask for capabilities called the bounding set. This mask controls which capabilities an executable can additionally get from the setuid bit (or from a file capability), and by emptying this set, this feature is effectively turned off. This mask does neither have an effect on the capabilities a process already has nor on the capabilities the process inherits to children. It just controls what can be gained by setting file attributes.

Unfortunately, there is no command to do this. We need to write a little C program dropbset.c:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <linux/prctl.h>
#include <linux/capability.h>
#include <errno.h>

int main (int argc, char *argv[]) {
        int code;
        unsigned long cap;

        for (cap=0; cap <= 63; cap++) {
                code = prctl(PR_CAPBSET_DROP, cap, 0, 0, 0);
                if (code == -1 && errno != EINVAL) {
                        perror("prctl error");
                        exit(1);
                }
        }

        code = execv("/bin/bash", argv);
        if (code == -1) {
                perror("exec error");
                exit(1);
        }
        exit(0);   // hmmmm...
}
The program can be simply compiled with
gcc -o dropbset dropbset.c
Before running it, we need to ensure it gets the required privileges to do the PR_CAPBSET_DROP operation. As root, do
setcap CAP_SETPCAP=pe dropbset
This command sets a file capability - a mechanism very much like the setuid bit, but restricted to a single capability only. Here, we need CAP_SETPCAP in order to run the operation. If you did not set this file capability, you could run dropbset only as root. (NB: The file capability is stored together with the file in the extended attributes.)

Now let's try it (remember dropbset just execs bash, and hence is called in exactly the same way): As non-root do:

./dropbset -c "ping 127.0.0.1"
You get an error "Operation not permitted". ping is normally setuid-installed so that unprivileged users get raw access to the network for pinging.

Once the bset is empty, there is no way a child process can add any capability to its effective set because of file attributes. Not even root can do this:

# id
uid=0(root) gid=0(root) groups=0(root)
# dropbset -c 'ping 127.0.0.1'
ping: icmp open socket: Operation not permitted
Gerd Stolpmann works as O'Caml consultant
This web site is published by Informatikbüro Gerd Stolpmann
Powered by Caml