about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2016-12-15T11·04+0100
committerEelco Dolstra <edolstra@gmail.com>2016-12-15T11·04+0100
commit9f3f2e21edb17dbcd674539dff96efb6cceca10c (patch)
tree8d687ca67b4729a91e32792bec3d8f35cda07ab8
parent47f587700d646f5b03a42f2fa57c28875a31efbe (diff)
parent4e1a2cd537b5b910937499c544043ddac291843e (diff)
Merge branch 'seccomp' of https://github.com/aszlig/nix
-rw-r--r--Makefile.config.in1
-rw-r--r--configure.ac9
-rw-r--r--release.nix7
-rw-r--r--src/libstore/build.cc94
-rw-r--r--src/libstore/local.mk4
-rw-r--r--tests/sandbox.nix54
6 files changed, 139 insertions, 30 deletions
diff --git a/Makefile.config.in b/Makefile.config.in
index a03776d572ae..08408ea5f3b0 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -10,6 +10,7 @@ OPENSSL_LIBS = @OPENSSL_LIBS@
 PACKAGE_NAME = @PACKAGE_NAME@
 PACKAGE_VERSION = @PACKAGE_VERSION@
 SODIUM_LIBS = @SODIUM_LIBS@
+LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@
 LIBLZMA_LIBS = @LIBLZMA_LIBS@
 SQLITE3_LIBS = @SQLITE3_LIBS@
 bash = @bash@
diff --git a/configure.ac b/configure.ac
index e6b11be2df19..4e1806270e90 100644
--- a/configure.ac
+++ b/configure.ac
@@ -193,6 +193,15 @@ AC_SUBST(HAVE_SODIUM, [$have_sodium])
 PKG_CHECK_MODULES([LIBLZMA], [liblzma], [CXXFLAGS="$LIBLZMA_CFLAGS $CXXFLAGS"])
 
 
+# Look for libseccomp, required for Linux sandboxing.
+if test "$sys_name" = linux; then
+  PKG_CHECK_MODULES([LIBSECCOMP], [libseccomp],
+                    [CXXFLAGS="$LIBSECCOMP_CFLAGS $CXXFLAGS"])
+#  AC_CHECK_LIB([seccomp], [seccomp_init], [true],
+#    [AC_MSG_ERROR([Nix requires libseccomp for sandboxing.  See https://github.com/seccomp/libseccomp.])])
+fi
+
+
 # Look for aws-cpp-sdk-s3.
 AC_LANG_PUSH(C++)
 AC_CHECK_HEADERS([aws/s3/S3Client.h],
diff --git a/release.nix b/release.nix
index d825dd583768..7c5e6125b09f 100644
--- a/release.nix
+++ b/release.nix
@@ -25,7 +25,7 @@ let
 
         buildInputs =
           [ curl bison flex perl libxml2 libxslt bzip2 xz
-            pkgconfig sqlite libsodium boehmgc
+            pkgconfig sqlite libsodium libseccomp boehmgc
             docbook5 docbook5_xsl
             autoconf-archive
           ] ++ lib.optional (!lib.inNixShell) git;
@@ -74,6 +74,7 @@ let
         buildInputs =
           [ curl perl bzip2 xz openssl pkgconfig sqlite boehmgc ]
           ++ lib.optional stdenv.isLinux libsodium
+          ++ lib.optional stdenv.isLinux libseccomp
           ++ lib.optional stdenv.isLinux
             (aws-sdk-cpp.override {
               apis = ["s3"];
@@ -196,6 +197,10 @@ let
       nix = build.x86_64-linux; system = "x86_64-linux";
     });
 
+    tests.sandbox = (import ./tests/sandbox.nix rec {
+      nix = build.x86_64-linux; system = "x86_64-linux";
+    });
+
     tests.binaryTarball =
       with import <nixpkgs> { system = "x86_64-linux"; };
       vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test"
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index c970fbdcaa65..f702433a02f2 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -54,6 +54,7 @@
 #include <sys/param.h>
 #include <sys/mount.h>
 #include <sys/syscall.h>
+#include <seccomp.h>
 #define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
 #endif
 
@@ -814,9 +815,6 @@ private:
        result. */
     ValidPathInfos prevInfos;
 
-    const uid_t sandboxUid = 1000;
-    const gid_t sandboxGid = 100;
-
 public:
     DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
         Worker & worker, BuildMode buildMode = bmNormal);
@@ -1644,8 +1642,56 @@ void chmod_(const Path & path, mode_t mode)
 }
 
 
+#if __linux__
+
+#define FORCE_SUCCESS(syscall) \
+    if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(0), SCMP_SYS(syscall), 0) != 0) { \
+        seccomp_release(ctx); \
+        throw SysError("unable to add seccomp rule for " #syscall); \
+    }
+
+void setupSeccomp(void) {
+    scmp_filter_ctx ctx;
+
+    if ((ctx = seccomp_init(SCMP_ACT_ALLOW)) == NULL)
+        throw SysError("unable to initialize seccomp mode 2");
+
+#if defined(__x86_64__)
+    if (seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0) {
+        seccomp_release(ctx);
+        throw SysError("unable to add 32bit seccomp architecture");
+    }
+#endif
+
+    FORCE_SUCCESS(chown32);
+    FORCE_SUCCESS(fchown32);
+    FORCE_SUCCESS(lchown32);
+
+    FORCE_SUCCESS(chown);
+    FORCE_SUCCESS(fchown);
+    FORCE_SUCCESS(fchownat);
+    FORCE_SUCCESS(lchown);
+
+    FORCE_SUCCESS(setxattr);
+    FORCE_SUCCESS(lsetxattr);
+    FORCE_SUCCESS(fsetxattr);
+
+    if (seccomp_load(ctx) != 0) {
+        seccomp_release(ctx);
+        throw SysError("unable to load seccomp BPF program");
+    }
+
+    seccomp_release(ctx);
+}
+
+#undef FORCE_SUCCESS
+
+#endif
+
+
 int childEntry(void * arg)
 {
+    setupSeccomp();
     ((DerivationGoal *) arg)->runChild();
     return 1;
 }
@@ -1965,18 +2011,14 @@ void DerivationGoal::startBuilder()
         createDirs(chrootRootDir + "/etc");
 
         writeFile(chrootRootDir + "/etc/passwd",
-            (format(
-                "root:x:0:0:Nix build user:/:/noshell\n"
-                "nixbld:x:%1%:%2%:Nix build user:/:/noshell\n"
-                "nobody:x:65534:65534:Nobody:/:/noshell\n") % sandboxUid % sandboxGid).str());
+            "root:x:0:0:Nix build user:/:/noshell\n"
+            "nobody:x:65534:65534:Nobody:/:/noshell\n");
 
         /* Declare the build user's group so that programs get a consistent
            view of the system (e.g., "id -gn"). */
         writeFile(chrootRootDir + "/etc/group",
-            (format(
-                "root:x:0:\n"
-                "nixbld:!:%1%:\n"
-                "nogroup:x:65534:\n") % sandboxGid).str());
+            "root:x:0:\n"
+            "nobody:x:65534:\n");
 
         /* Create /etc/hosts with localhost entry. */
         if (!fixedOutput)
@@ -2160,12 +2202,7 @@ void DerivationGoal::startBuilder()
         Pid helper = startProcess([&]() {
 
             /* Drop additional groups here because we can't do it
-               after we've created the new user namespace.  FIXME:
-               this means that if we're not root in the parent
-               namespace, we can't drop additional groups; they will
-               be mapped to nogroup in the child namespace. There does
-               not seem to be a workaround for this. (But who can tell
-               from reading user_namespaces(7)?)*/
+               after we've created the new user namespace. */
             if (getuid() == 0 && setgroups(0, 0) == -1)
                 throw SysError("setgroups failed");
 
@@ -2198,19 +2235,19 @@ void DerivationGoal::startBuilder()
         if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort();
         pid = tmp;
 
-        /* Set the UID/GID mapping of the builder's user namespace
-           such that the sandbox user maps to the build user, or to
-           the calling user (if build users are disabled). */
-        uid_t hostUid = buildUser.enabled() ? buildUser.getUID() : getuid();
-        uid_t hostGid = buildUser.enabled() ? buildUser.getGID() : getgid();
+        /* Set the UID/GID mapping of the builder's user
+           namespace such that root maps to the build user, or to the
+           calling user (if build users are disabled). */
+        uid_t targetUid = buildUser.enabled() ? buildUser.getUID() : getuid();
+        uid_t targetGid = buildUser.enabled() ? buildUser.getGID() : getgid();
 
         writeFile("/proc/" + std::to_string(pid) + "/uid_map",
-            (format("%d %d 1") % sandboxUid % hostUid).str());
+            (format("0 %d 1") % targetUid).str());
 
         writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
 
         writeFile("/proc/" + std::to_string(pid) + "/gid_map",
-            (format("%d %d 1") % sandboxGid % hostGid).str());
+            (format("0 %d 1") % targetGid).str());
 
         /* Signal the builder that we've updated its user
            namespace. */
@@ -2420,12 +2457,11 @@ void DerivationGoal::runChild()
             if (rmdir("real-root") == -1)
                 throw SysError("cannot remove real-root directory");
 
-            /* Switch to the sandbox uid/gid in the user namespace,
-               which corresponds to the build user or calling user in
-               the parent namespace. */
-            if (setgid(sandboxGid) == -1)
+            /* Become root in the user namespace, which corresponds to
+               the build user or calling user in the parent namespace. */
+            if (setgid(0) == -1)
                 throw SysError("setgid failed");
-            if (setuid(sandboxUid) == -1)
+            if (setuid(0) == -1)
                 throw SysError("setuid failed");
 
             setUser = false;
diff --git a/src/libstore/local.mk b/src/libstore/local.mk
index 9d5c04dca0c5..a8222025cf7e 100644
--- a/src/libstore/local.mk
+++ b/src/libstore/local.mk
@@ -18,6 +18,10 @@ ifeq ($(OS), SunOS)
 	libstore_LDFLAGS += -lsocket
 endif
 
+ifeq ($(OS), Linux)
+	libstore_LDFLAGS += -lseccomp
+endif
+
 libstore_CXXFLAGS = \
  -DNIX_PREFIX=\"$(prefix)\" \
  -DNIX_STORE_DIR=\"$(storedir)\" \
diff --git a/tests/sandbox.nix b/tests/sandbox.nix
new file mode 100644
index 000000000000..dc72a5985ef7
--- /dev/null
+++ b/tests/sandbox.nix
@@ -0,0 +1,54 @@
+# Test Nix builder sandbox.
+
+{ system, nix }:
+
+with import <nixpkgs/nixos/lib/testing.nix> { inherit system; };
+
+let
+  mkUtils = pkgs: pkgs.buildEnv {
+    name = "sandbox-utils";
+    paths = [ pkgs.coreutils pkgs.utillinux pkgs.bash ];
+    pathsToLink = [ "/bin" "/sbin" ];
+  };
+
+  utils32 = mkUtils pkgs.pkgsi686Linux;
+  utils64 = mkUtils pkgs;
+
+  sandboxTestScript = pkgs.writeText "sandbox-testscript.sh" ''
+    [ $(id -u) -eq 0 ]
+    cp -p "$testfile" foo
+    chown 1024:1024 foo
+    touch "$out"
+  '';
+
+  testExpr = arch: pkgs.writeText "sandbox-test.nix" ''
+    let
+      utils = builtins.storePath
+        ${if arch == "i686-linux" then utils32 else utils64};
+    in derivation {
+      name = "sandbox-test";
+      system = "${arch}";
+      builder = "''${utils}/bin/bash";
+      args = ["-e" ${sandboxTestScript}];
+      PATH = "''${utils}/bin";
+      testfile = builtins.toFile "test" "i am a test file";
+    }
+  '';
+
+in makeTest {
+  name = "nix-sandbox";
+
+  machine = { pkgs, ... }: {
+    nix.package = nix;
+    nix.useSandbox = true;
+    nix.binaryCaches = [];
+    virtualisation.writableStore = true;
+    virtualisation.pathsInNixDB = [ utils32 utils64 ];
+  };
+
+  testScript = ''
+    $machine->waitForUnit("multi-user.target");
+    $machine->succeed("nix-build ${testExpr "x86_64-linux"}");
+    $machine->succeed("nix-build ${testExpr "i686-linux"}");
+  '';
+}