24

Is it possible for a non-root user to run a chroot process on Ubuntu?

5
  • This old FreeBSD thread covers the same question: lists.freebsd.org/pipermail/freebsd-security/2003-April/… Short answer: No, you cannot run a process as root within a non-root chroot jail. Apr 25, 2010 at 9:19
  • 1
    chroot jails are specific to bsd. a chroot in linux is not a jail. Last I checked it was not possible to chroot as a user. Apr 25, 2010 at 10:11
  • 2
    @xenoterracide Jails are BSD specific, but chroot is commonly known as a "chroot jail" in the Linux community. It's quite confused.
    – pehrs
    Apr 25, 2010 at 19:46
  • 2
    What are you trying to do and why? There are tools like fakechroot, and schroot that make provide a workable alternative depending on your requirements.
    – Zoredache
    Apr 25, 2010 at 22:32
  • There was also more related discussion at How to “jail” a process without being root? with more working or tentative approaches to solving this task listed. May 21, 2011 at 5:15

6 Answers 6

19

On Linux the chroot(2) system call can only be made by a process that is privileged. The capability the process needs is CAP_SYS_CHROOT.

The reason you can't chroot as a user is pretty simple. Assume you have a setuid program such as sudo that checks /etc/sudoers if you are allowed to do something. Now put it in a chroot chroot with your own /etc/sudoers. Suddenly you have an instant privilege escalation.

It is possible to design a program to chroot itself and run it as a setuid process, but this is generally considered bad design. The extra security of the chroot does not motivate the security issues with the setuid.

3
  • 6
    With the new possibilities of namespaces in linux, perhaps it's possible to create (unshare) a new "user" namespace, where there would be an "embedded" root user, and perform chroot then. Jan 10, 2013 at 16:46
  • 2
    @imz--IvanZakharyaschev You're absolutely correct, and I hope you don't mind me having taken the liberty of writing that up as an easily testable answer.
    – hvd
    Dec 2, 2014 at 19:28
  • @hvd Great! It must be a very useful, because it demonstrates how to use the new unfamiliar Linux features with concrete commands. Dec 3, 2014 at 9:36
14

@imz--IvanZakharyaschev comments on pehrs's answer that it may be possible with the introduction of namespaces, but this hasn't been tested and posted as an answer. Yes, that does indeed make it possible for a non-root user to use chroot.

Given a statically-linked dash, and a statically-linked busybox, and a running bash shell running as non-root:

$ mkdir root
$ cp /path/to/dash root
$ cp /path/to/busybox root
$ unshare -r bash -c 'chroot root /dash -c "/busybox ls -al /"'
total 2700
drwxr-xr-x    2 0        0             4096 Dec  2 19:16 .
drwxr-xr-x    2 0        0             4096 Dec  2 19:16 ..
drwxr-xr-x    1 0        0          1905240 Dec  2 19:15 busybox
drwxr-xr-x    1 0        0           847704 Dec  2 19:15 dash

The root user ID in that namespace is mapped to the non-root user ID outside of that namespace, and vice versa, which is why the system shows files owned by the current user as owned by user ID 0. A regular ls -al root, without unshare, does show them as owned by the current user.


Note: it's well-known that processes that are capable of using chroot, are capable of breaking out of a chroot. Since unshare -r would grant chroot permissions to an ordinary user, it would be a security risk if that was allowed inside a chroot environment. Indeed, it is not allowed, and fails with:

unshare: unshare failed: Operation not permitted

which matches the unshare(2) documentation:

EPERM (since Linux 3.9)

CLONE_NEWUSER was specified in flags and the caller is in a chroot environment (i.e., the caller's root directory does not match the root directory of the mount namespace in which it resides).

2
  • Running pivot_root in a mount namespace has a similar effect to chroot but avoids the conflict with user namespaces. Aug 30, 2018 at 16:42
  • 1
    One can escape a chroot or mount namespace by descending into /proc if their is a process outside with same UID in the same or child PID and user namespaces. Aug 30, 2018 at 17:50
4

These days, you want to be looking at LXC (Linux Containers) instead of chroot/BSD jail. It's somewhere between a chroot and a virtual machine, giving you a lot of security control and general configurability. I believe all you need to run it as a user is to be a member of the group that owns the necessary files/devices, but there might also be capabilities/system permissions involved. Either way, it should be very doable, since LXC is quite recent, long after SELinux etc. was added to the Linux kernel.

Also, bear in mind that you can just write scripts as root but give users secure permission to run those scripts (without a password if you like, but make sure the script is secure) using sudo.

2

The combination of fakeroot / fakechroot gives a simulacre of chroot for simple needs such as producing tar archives where files appear to be owned by root. Fakechroot manpage is http://linux.die.net/man/1/fakechroot.

You don't get any new permission though, but if you own a directory (e.g. fake-distro) before invoking

fakechroot fakeroot chroot ~/fake-distro some-command

it now look for some-command like you're root and owning everything within fake-distro.

2
  • This is a nice idea, but it seems to handle symlinks unpredictably. My ~/fake-distro uses busybox, which symlinks ls, mv and other common utilities to /bin/busybox. If I explicitly call /bin/busybox mv ..., things work, but if I call /bin/mv ... I get sh: /bin/mv: not found. Setting export FAKECHROOT_EXCLUDE_PATH=/ before running the fakechroot fixes that symptom, but then it breaks on other symlinks (e.g. /usr/bin/vim -> /usr/bin/vim.vim). Sep 8, 2017 at 17:31
  • maybe FAKECHROOT_EXCLUDE_PATH=/:/usr would help, then ?
    – sylvainulg
    Sep 13, 2017 at 14:40
2

It seems that with user-namespaces it is in fact possible to chroot without root. Here is an example program which demonstrates that it is possible. I have only begun to explore how linux namespaces work and so I'm not entirely sure if this code is best practice or not.

Save as user_chroot.cc. Compile with g++ -o user_chroot user_chroot.cc. Usage is ./user_chroot /path/to/new_rootfs.

// references:
// [1]: http://man7.org/linux/man-pages/man7/user_namespaces.7.html
// [2]: http://man7.org/linux/man-pages/man2/unshare.2.html

#include <sched.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <cerrno>
#include <cstdio>
#include <cstring>

int main(int argc, char** argv) {
    if(argc < 2) {
        printf("Usage: %s <rootfs>\n", argv[0]);
    }

    int uid = getuid();
    int gid = getgid();
    printf("Before unshare, uid=%d, gid=%d\n", uid, gid);

    // First, unshare the user namespace and assume admin capability in the
    // new namespace
    int err = unshare(CLONE_NEWUSER);
    if(err) {
        printf("Failed to unshare user namespace\n");
        return 1;
    }

    // write a uid/gid map
    char file_path_buf[100];
    int pid = getpid();
    printf("My pid: %d\n", pid);

    sprintf(file_path_buf, "/proc/%d/uid_map", pid);
    int fd = open(file_path_buf, O_WRONLY);
    if(fd == -1) {
        printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno, 
               strerror(errno));
    } else {
        printf("Writing : %s (fd=%d)\n", file_path_buf, fd);
        err = dprintf(fd, "%d %d 1\n", uid, uid);
        if(err == -1) {
            printf("Failed to write contents [%d]: %s\n", errno, 
                   strerror(errno));
        }
        close(fd);
    }

    sprintf(file_path_buf, "/proc/%d/setgroups", pid);
    fd = open(file_path_buf, O_WRONLY);
    if(fd == -1) {
        printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno, 
               strerror(errno));
    } else {
        dprintf(fd, "deny\n");
        close(fd);
    }

    sprintf(file_path_buf, "/proc/%d/gid_map", pid);
    fd = open(file_path_buf, O_WRONLY);
    if(fd == -1) {
        printf("Failed to open %s for write [%d] %s\n", file_path_buf, errno, 
               strerror(errno));
    } else {
        printf("Writing : %s (fd=%d)\n", file_path_buf, fd);
        err = dprintf(fd, "%d %d 1\n", gid, gid);
        if(err == -1) {
            printf("Failed to write contents [%d]: %s\n", errno, 
                   strerror(errno));
        }
        close(fd);
    }

    // Now chroot into the desired directory
    err = chroot(argv[1]);
    if(err) {
        printf("Failed to chroot\n");
        return 1;
    }

    // Now drop admin in our namespace
    err = setresuid(uid, uid, uid);
    if(err) {
        printf("Failed to set uid\n");
    }

    err = setresgid(gid, gid, gid);
    if(err) {
        printf("Failed to set gid\n");
    }

    // and start a shell
    char argv0[] = "bash";
    char* new_argv[] = {
        argv0,
        NULL
    };

    err = execvp("/bin/bash", new_argv);
    if(err) {
        perror("Failed to start shell");
        return -1;
    }
}

I have tested this on a minimal rootfs generated with multistrap (executed as non-root). Some system files like /etc/passwd and /etc/groups were copied from the host rootfs into the guest rootfs.

3
0

No. If I recall correctly there is some kernel level thing that chroot does that prevents it. I don't recall what that thing was. I investigated it back when messing with Gentoo's Catalyst Build tool (and a chroot on gentoo is the same as a chroot on ubuntu). Though it would be possible to make it happen without a passwd... but such things are left to the realm of potential security vulnerabilities and making sure you know what you are doing.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .