Trips of a curious penguin.

Hello, time traveler! You are reading an article that is almost ten years old. The world has changed, and so have I and my opinions. There is a good chance what's below is not current, correct, or secure anymore, and maybe it never was. This page is preserved because I am an archivist at heart, but you have been warned.

Escaping a chroot jail/1

Everybody will tell you that a chroot jail (that is, making a process think that a directory is instead the root folder, and not letting it access or modify anything outside of that) is ineffective against a process with root privileges1 (UID 0). Let’s see why.

The escape basically works like this:

  • We create a temporary folder (I named mine .42, hidden not to draw too much attention) and we chroot to that, this way we make sure our current working directory is outside the fake root, and we can do so because we’re CEOroot, Bitch2;

  • then we chroot to parent folders all the way up to the root (we don’t need to worry about going too up, /../../.. == /);

  • finally we spawn something, a shell, rm -rf, whatever.

Q: Why couldn’t we just do chroot("../../../../../../..") and call it a day?
A: Because even if the kernel does not want to keep us from doing what we want (we’re root, after all) it will keep faith to the chroot also with us and if from inside the chroot jail we ask to chroot("..") the kernel will regularly expand /.. to /. It has to do so, some programs might rely on that. So we have to move our working directory outside of the root before proceeding.

Other pitfalls

If chroot() changes also the working directory to be inside the jail this will make it impossible to pop outside by just chrooting to a sub-directory, but this will not stop us.

We can simply grab the file descriptor of the current directory before the first chroot call and then fchdir() to that. chroot() does not close file descriptors.

Also, if the root privileges were incorrectly dropped, for example by calling seteuid(), a call to setuid(0) might be useful in restoring them.

So, how does a correct chroot look like?

assert(UID > 0);

And make sure that there are no setuid binaries inside the jail3!

A catch-all compile-everywhere C unchroot

  1. Or even just the CAP_SYS_CHROOT privilege (that self-chroot jailing processes often forget to drop), most of the cases we just need to be able to run chroot().

  2. Ahem.

  3. find / -type f \( -perm -4000 -o -perm -2000 \)