[Nix-dev] Making Nix use pivot_root in addition to chroot

Harald van Dijk harald at gigawatt.nl
Thu Dec 4 12:24:21 CET 2014


Hello,

I've recently been trying to use Nix in a somewhat different way than 
NixOS, and the builders I've got, it would be useful if they were able 
to run unshare -r. Unfortunately, while this command is normally 
available to unprivileged users, it is for security reasons not 
available to unprivileged users in a chroot environment.

Reading up on how this works, I've come to understand that as one effect 
of unshare -r is enabling chroot for unprivileged users, and the chroot 
capability is well-known as being sufficient to break out of a chroot 
directory, those security concerns are correct, and it is appropriate 
for unshare -r to report an error if Nix is configured to use chroot.

However, if chroot is combined with pivot_root, then the mount namespace 
root and the process root are the same directory again. Since there is 
no longer anything nothing to break out of, there is no risk of a user 
breaking out of anything, and then, unshare -r _does_ work.

A possible problem with pivot_root could have been that it lets the old 
root remain available inside the new root, but that is easily prevented 
by unmounting it afterwards.

The attached patch (to Nix 1.7) to use pivot_root is mainly a 
proof-of-concept, I know it is not appropriate in its current form. At 
the very least, it should properly handle systems that lack a pivot_root 
syscall, and even on systems that do have it, it might be better to make 
the use of pivot_root optional (compile-time as well as run-time).

Nonetheless, in its current form it is already useful for testing, which 
has shown that it addresses the problem for me that I had hoped for it 
to address, and that it does not cause problems for NixOS packages, 
which I do continue to use alongside my own.

Is there any interest in getting something like this in Nix? I would be 
perfectly happy to clean this patch up, get it into better shape, but 
I'd like to avoid doing so if (for whatever reason) it is decided that 
Nix should not be using this.

Cheers,
Harald van Dijk
-------------- next part --------------
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -51,6 +51,7 @@
 #define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS)
 
 #if CHROOT_ENABLED
+#include <sys/syscall.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
 #include <net/if.h>
@@ -2021,6 +2022,11 @@ void DerivationGoal::initChild()
                     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()) {
@@ -2093,13 +2099,26 @@ void DerivationGoal::initChild()
 		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
 


More information about the nix-dev mailing list