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
chrootto that, this way we make sure our current working directory is outside the fake root, and we can do so because we’re
chrootto 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
/. 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.
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.
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?
1 2 3 4