By Jake Edge
October 3, 2007
The chroot() system call
is often misunderstood, as it can appear to do much more than it
actually does. The confusion arises because it appears to some to be a
security tool – there are limited security uses – when it is
meant as a tool for isolating processes for installation, debugging, and
legacy library usage.
What chroot() actually does is fairly simple, it modifies
pathname lookups for a process and its children so that any reference to a
path starting '/' will effectively have the new root, which is passed as
the single argument, prepended onto the path. The current working
directory is left unchanged and relative paths can still refer to files
outside of the new root.
Calls to chroot()
do not stack, with additional calls essentially overwriting the existing
one. It can only be called by privileged programs and can be trivially
bypassed by those who can call it as man 2 chroot describes:
This call does not change the current working directory, so that after
the call '.' can be outside the tree rooted at '/'. In particular, the
superuser can escape from a 'chroot jail' by doing 'mkdir foo; chroot
foo; cd ..'.
The use of the term "chroot jail" in the manpage is unfortunate as it may
help perpetuate a common misconception about chroot(). It often
gets mentioned in the same context as the "jail" calls
for the BSDs, but it has little in common with them. A BSD jail is a
mini-virtualization that partitions a system into multiple virtual systems
each of which can have its own root account. chroot() has none of
that sophistication.
A patch posted to the linux-kernel
mailing list was aimed at fixing the "hole" described in the manpage, but
led, instead, to a rather contentious thread. The patch changes
chroot() by setting the current working directory to the new root
if it was not already somewhere underneath it. This violates POSIX and
other standards, which specify the current behavior, as well as numerous
typical use cases for chroot(). In addition, as was forcefully
pointed out in the thread, there are innumerable ways for a privileged
process to access files that are not underneath the new root. Even if it
did not run afoul of the standards, there is no point in fixing something
that is so trivially bypassed in other ways.
The proponents of fixing the problem that they see – even if most of
the kernel hackers disagree – seem to
believe that, while you can circumvent a chroot() call, it should
not be possible by using chroot() itself. It is an argument that
didn't seem to get anywhere for a pretty simple reason: chroot()
is not meant to be a security-oriented access control mechanism. It is,
instead, a way to run processes with a partitioned view of the filesystem.
There are reasonable uses of chroot() for very limited
security purposes. Daemons that do not run as root can be placed into
their own filesystem subtree – bind/named and Apache are sometimes
run this way – to prevent any access outside of it. That will work,
even if the daemon gets exploited, as long as there is no way to elevate
privileges after the exploit. For example, if there are vulnerable
setuid() programs accessible from within the chroot(),
full filesystem access is possible.
chroot() is a useful call, many install programs use it, as do
programs that need to see a consistent set of older libraries, but it has
very limited use in security applications. It does not provide a
sandbox that can be used to test suspicious code, that code might escalate
its privilege and access anything it wished. Maintaining an up-to-date
chroot() environment adds an additional burden on administrators
as well; update tools do nothing to help keep utilities secure if they live
outside of the normal places. It is probably safest to avoid using it as
any kind of security tool.
(
Log in to post comments)