index

Article posted on: 2023-02-21 21:53
Last edited on: 2023-02-21 23:15
Written by: Sylvain Gauthier

chroot in C without root access (Linux)

Here is a simple way to containerize your program into a directory, that is more secure than chroot because it uses pivot_root and doesn’t require root privileges thanks to Linux' namespaces.

See this great article for more details on how namespaces work.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/mount.h>
#include <linux/sched.h>

/* see pivot_root(2) man page example for a detailed description of all the
 * steps
 */

int dir_jail(const char* root) {
    char* oldRoot = "/old_root";
    char oldPath[1024];
    int ok = 0;

    if (strlen(root) + strlen(oldRoot) + 1 > sizeof(oldPath)) {
        fprintf(stderr, "Error: path too long\n");
    }
    snprintf(oldPath, sizeof(oldPath), "%s%s", root, oldRoot);

    if (syscall(SYS_unshare, CLONE_NEWUSER | CLONE_NEWNS) != 0) {
        fprintf(stderr, "unshare: %s\n", strerror(errno));
    } else if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) {
        fprintf(stderr, "mount: %s\n", strerror(errno));
    } else if (mount(root, root, NULL, MS_BIND, NULL) != 0) {
        fprintf(stderr, "bind: %s\n", strerror(errno));
    } else if (mkdir(oldPath, 0777) != 0) {
        fprintf(stderr, "mkdir: failed to create %s: %s\n",
                oldPath, strerror(errno));
    } else if (syscall(SYS_pivot_root, root, oldPath) != 0) {
        fprintf(stderr, "pivot_root: %s\n", strerror(errno));
    } else if (chdir("/") != 0) {
        fprintf(stderr, "chdir: %s\n", strerror(errno));
    } else if (umount2(oldRoot, MNT_DETACH)) {
        fprintf(stderr, "umount2: %s\n", strerror(errno));
    } else if (rmdir(oldRoot) != 0) {
        fprintf(stderr, "rmdir: %s\n", strerror(errno));
    } else {
        ok = 1;
    }
    return ok;
}

After calling dir_jail(dir), your program sees dir as / and cannot escape it. It will have a full set of capabilities and can do pretty much anything root can with some major exceptions. As detailed in the article, unless it’s started as root, it will not be able to mount block devices. And it will not be able to nest another container in itself, unless you go through the uid/gid mapping process, which is quite annoying. See user_namespaces(7) on how to do this.

Nevertheless, it should provide some robust security for a server application or a CGI process, without having to run the calling process as root.