1

I have a directory /var/mychoot on the same filesystem as /, and I've started the program /var/mychroot/prog as sudo chroot /var/mychroot /prog, so the program is running as EUID 0.

If the program executes the chdir("..") escape technique, then it is able to escape the chroot and see everything within /. (I've verified this on Linux 4.18.)

I want to prevent such an escape. In fact I want to prevent all kinds of chroot escapes, but in this question I'm only interested in how the chdir("..") escape technique can be prevented on modern Linux systems. For this I'm looking for alternatives of the chroot(2) system call.

I've found 2 solutions: pivot_root and MS_MOVE, but they only work if /var/mychroot is a mount point, so they fail if /var/mychroot is just a subdirectory within the / filesystem. Is there another solution in this case?

I want to avoid techniques using LD_PRELOAD (because LD_PRELOAD doesn't affect statically linked executables), techniques using ptrace(2) (because then I'm not able to run strace within the chroot, and also ptrace(2) is very tricky to get right: processes will crash or hang) and real virtualization (e.g. Xen or KVM or QEMU; because of the performance overhead and the less flexible memory provisioning).

To recap, I need:

  • an alternative of chroot(2) system call,
  • with which root can restrict processes running as root (EUID 0),
  • to a subdirectory of the filesystem of /,
  • which prevents the chdir("..") escape technique,
  • and doesn't use LD_PRELOAD or
  • ptrace(2) or
  • virtualization (e.g. Xen, KVM or QEMU),
  • and it runs on a modern Linux system,
  • with and unpatched kernel.

Does it exist?

  • Why would you need root to run in a jail? Usually you'd drop root privileges once set up and depend on services or setuid exes for access to privileged calls and areas (e.g., network). – melds Jan 5 at 2:17
  • 1
    @melds: For example, running in a jail as root is useful if apt-get install UNTRUSTED-PACKAGE is run, where the the install script of the package can contain malicious code. A similar use case is docker run UNTRUSTED-IMAGE, the default commands of such packages run as root. Docker is not using chroot for filesystem isolation, and I'm also interested in understanding what would break if it did, and how it could be fixed. – pts Jan 5 at 2:29
2

To protect against the specific chdir("..") escape technique you mentioned, you can simply drop the capability to execute chroot(2) again once you're chrooted to /var/mychroot yourself. The escape technique requires another call to chroot(), so blocking that is enough to prevent it from working.

You can do that with Linux capabilities, by dropping CAP_SYS_CHROOT which is the one needed for chroot(2) to be available.

For example:

outside# chroot /var/mychroot
inside# capsh --drop=cap_sys_chroot --
inside# ./escape_chroot
chroot(baz): Operation not permitted

(The second prompt inside the chroot is from a shell spawned from capsh. You can make it run another command with, for example, capsh --drop=cap_sys_chroot -- -c 'exec ./escape_chroot'.)


But a much better technique is to just use pivot_root, since it protects from many of the other possible exploits that chroot(2) will not protect from.

You mentioned that it only works if /var/mychroot is a mount point, but you can make it a mount point by simply bind mounting it into itself.

Note that you need to create a mount namespace to use pivot_root to create a jail, otherwise it will try to change the root of all processes in your filesystem, which is most likely not what you want here...

So the whole sequence goes:

outside# unshare -m
outside# mount --bind /var/mychroot /var/mychroot
outside# cd /var/mychroot
outside# mkdir old_root
outside# pivot_root . old_root
limbo# exec chroot .
inside# umount /old_root

(Again, many of these commands are spawning new shells. unshare does that, so does chroot itself. You can work around those by passing commands as extra arguments. In some cases you might want to pass sh -c '...' for a full script.)

At this point, you're inside a pivot_root jail in a separate mount namespace, and the fact that /var/mychroot is simply a directory of the original root (and not a mount of a separate device or loop device) didn't really prevent this from working, thanks to the bind mount into itself.

Running the escape code, you'll see that the jail works as expected (even though the escape code claims otherwise):

inside# touch /inside_jail
inside# ./escape_chroot
Exploit seems to work. =)
inside# # ls -ld /inside_jail /old_root
-rw-r--r--.  1 0 0    0 Jan  5 23:45 /inside_jail
dr-xr-xr-x. 20 0 0 4096 Jul  5 23:45 /old_root

As you can see, still inside the jail... The exploit code is just a bit naive and assumes that as long as the operations (chroot, chdir) have succeeded, that was enough to escape the jail, which is not really the case...

So consider using this technique to create a jail that is superior to chroot and does not require using Linux capabilities to block operations inside it (such as creating additional chroots, which might actually be useful or required for what you're actually trying to run in a jail.)

  • 1
    FYI I've found the command-line tool jchroot which automates this pivot_root(2) technique. I've verified on Linux 3.13 and 4.18 that the chdir("..") escape technique doesn't work with this pivot_root(2) technique as used by jchroot in github.com/vincentbernat/jchroot/blob/… – pts Jan 5 at 12:47
3

they only work if /var/mychroot is a mount point, so they fail if /var/mychroot is just a subdirectory within the / filesystem.

You can make any directory a mount point: mount --rbind /var/mychoot /var/mychoot.

This step - and others that you will need - are covered here:

How to perform chroot with Linux namespaces?

In fact I want to prevent all kinds of chroot escapes

Combine the above with user namespaces.

Alternatively, seccomp. This is how Docker prevents contained processes calling mount(), for example. (When you mount a block device a second time, you get a second view of that filesystem, which more or less counts as an escape). Copy Docker's list of allowed syscalls - root has lots of different ways of escaping.

https://docs.docker.com/engine/security/seccomp/

https://github.com/moby/moby/blob/master/profiles/seccomp/default.json

  • 1
    Thank you for both the solution and the insightful links on hardening a containers and chroot environments. Unfortunately it's not possible to accept multiple answers, but you have my very keen upvote. – pts Jan 5 at 12:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.