Skip to content

Commit

Permalink
Use pivot_root in addition to chroot when possible
Browse files Browse the repository at this point in the history
chroot only changes the process root directory, not the mount namespace root
directory, and it is well-known that any process with chroot capability can
break out of a chroot "jail". By using pivot_root as well, and unmounting the
original mount namespace root directory, breaking out becomes impossible.

Non-root processes typically have no ability to use chroot() anyway, but they
can gain that capability through the use of clone() or unshare(). For security
reasons, these syscalls are limited in functionality when used inside a normal
chroot environment. Using pivot_root() this way does allow those syscalls to be
put to their full use.
  • Loading branch information
hvdijk authored and edolstra committed Feb 16, 2015
1 parent b0bad3e commit 5451b8d
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 7 deletions.
1 change: 1 addition & 0 deletions configure.ac
Expand Up @@ -87,6 +87,7 @@ AC_CHECK_HEADERS([sys/mount.h], [], [],
# include <sys/param.h>
# endif
])
AC_CHECK_HEADERS([sys/syscall.h])


# Check for lutimes, optionally used for changing the mtime of
Expand Down
35 changes: 28 additions & 7 deletions src/libstore/build.cc
Expand Up @@ -38,6 +38,9 @@
#if HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif
#if HAVE_SYS_SYSCALL_H
#include <sys/syscall.h>
#endif
#if HAVE_SCHED_H
#include <sched.h>
#endif
Expand All @@ -48,7 +51,7 @@
#include <linux/fs.h>
#endif

#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS)
#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS) && defined(SYS_pivot_root)

/* chroot-like behavior from Apple's sandbox */
#if __APPLE__
Expand Down Expand Up @@ -2059,6 +2062,11 @@ void DerivationGoal::runChild()
throw SysError(format("unable to make filesystem ‘%1%’ private") % fs);
}

/* Bind-mount chroot directory to itself, to treat it as a
different filesystem from /, as needed for pivot_root. */
if (mount(chrootRootDir.c_str(), chrootRootDir.c_str(), 0, MS_BIND, 0) == -1)
throw SysError(format("unable to bind mount ‘%1%’") % chrootRootDir);

/* Set up a nearly empty /dev, unless the user asked to
bind-mount the host /dev. */
if (dirsInChroot.find("/dev") == dirsInChroot.end()) {
Expand Down Expand Up @@ -2130,13 +2138,26 @@ void DerivationGoal::runChild()
chmod_(chrootRootDir + "/dev/pts/ptmx", 0666);
}

/* Do the chroot(). Below we do a chdir() to the
temporary build directory to make sure the current
directory is in the chroot. (Actually the order
doesn't matter, since due to the bind mount tmpDir and
tmpRootDit/tmpDir are the same directories.) */
if (chroot(chrootRootDir.c_str()) == -1)
/* Do the chroot(). */
if (chdir(chrootRootDir.c_str()) == -1)
throw SysError(format("cannot change directory to ‘%1%’") % chrootRootDir);

if (mkdir("real-root", 0) == -1)
throw SysError("cannot create real-root directory");

#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
if (pivot_root(".", "real-root") == -1)
throw SysError(format("cannot pivot old root directory onto ‘%1%’") % (chrootRootDir + "/real-root"));
#undef pivot_root

if (chroot(".") == -1)
throw SysError(format("cannot change root directory to ‘%1%’") % chrootRootDir);

if (umount2("real-root", MNT_DETACH) == -1)
throw SysError("cannot unmount real root filesystem");

if (rmdir("real-root") == -1)
throw SysError("cannot remove real-root directory");
}
#endif

Expand Down

0 comments on commit 5451b8d

Please sign in to comment.