This commit is contained in:
Eelco Dolstra 2016-12-15 12:04:45 +01:00
commit 9f3f2e21ed
No known key found for this signature in database
GPG key ID: 8170B4726D7198DE
6 changed files with 139 additions and 30 deletions

View file

@ -10,6 +10,7 @@ OPENSSL_LIBS = @OPENSSL_LIBS@
PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_VERSION = @PACKAGE_VERSION@ PACKAGE_VERSION = @PACKAGE_VERSION@
SODIUM_LIBS = @SODIUM_LIBS@ SODIUM_LIBS = @SODIUM_LIBS@
LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@
LIBLZMA_LIBS = @LIBLZMA_LIBS@ LIBLZMA_LIBS = @LIBLZMA_LIBS@
SQLITE3_LIBS = @SQLITE3_LIBS@ SQLITE3_LIBS = @SQLITE3_LIBS@
bash = @bash@ bash = @bash@

View file

@ -193,6 +193,15 @@ AC_SUBST(HAVE_SODIUM, [$have_sodium])
PKG_CHECK_MODULES([LIBLZMA], [liblzma], [CXXFLAGS="$LIBLZMA_CFLAGS $CXXFLAGS"]) 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. # Look for aws-cpp-sdk-s3.
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
AC_CHECK_HEADERS([aws/s3/S3Client.h], AC_CHECK_HEADERS([aws/s3/S3Client.h],

View file

@ -25,7 +25,7 @@ let
buildInputs = buildInputs =
[ curl bison flex perl libxml2 libxslt bzip2 xz [ curl bison flex perl libxml2 libxslt bzip2 xz
pkgconfig sqlite libsodium boehmgc pkgconfig sqlite libsodium libseccomp boehmgc
docbook5 docbook5_xsl docbook5 docbook5_xsl
autoconf-archive autoconf-archive
] ++ lib.optional (!lib.inNixShell) git; ] ++ lib.optional (!lib.inNixShell) git;
@ -74,6 +74,7 @@ let
buildInputs = buildInputs =
[ curl perl bzip2 xz openssl pkgconfig sqlite boehmgc ] [ curl perl bzip2 xz openssl pkgconfig sqlite boehmgc ]
++ lib.optional stdenv.isLinux libsodium ++ lib.optional stdenv.isLinux libsodium
++ lib.optional stdenv.isLinux libseccomp
++ lib.optional stdenv.isLinux ++ lib.optional stdenv.isLinux
(aws-sdk-cpp.override { (aws-sdk-cpp.override {
apis = ["s3"]; apis = ["s3"];
@ -196,6 +197,10 @@ let
nix = build.x86_64-linux; system = "x86_64-linux"; 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 = tests.binaryTarball =
with import <nixpkgs> { system = "x86_64-linux"; }; with import <nixpkgs> { system = "x86_64-linux"; };
vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test" vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test"

View file

@ -54,6 +54,7 @@
#include <sys/param.h> #include <sys/param.h>
#include <sys/mount.h> #include <sys/mount.h>
#include <sys/syscall.h> #include <sys/syscall.h>
#include <seccomp.h>
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old)) #define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
#endif #endif
@ -814,9 +815,6 @@ private:
result. */ result. */
ValidPathInfos prevInfos; ValidPathInfos prevInfos;
const uid_t sandboxUid = 1000;
const gid_t sandboxGid = 100;
public: public:
DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs, DerivationGoal(const Path & drvPath, const StringSet & wantedOutputs,
Worker & worker, BuildMode buildMode = bmNormal); 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) int childEntry(void * arg)
{ {
setupSeccomp();
((DerivationGoal *) arg)->runChild(); ((DerivationGoal *) arg)->runChild();
return 1; return 1;
} }
@ -1965,18 +2011,14 @@ void DerivationGoal::startBuilder()
createDirs(chrootRootDir + "/etc"); createDirs(chrootRootDir + "/etc");
writeFile(chrootRootDir + "/etc/passwd", writeFile(chrootRootDir + "/etc/passwd",
(format( "root:x:0:0:Nix build user:/:/noshell\n"
"root:x:0:0:Nix build user:/:/noshell\n" "nobody:x:65534:65534:Nobody:/:/noshell\n");
"nixbld:x:%1%:%2%:Nix build user:/:/noshell\n"
"nobody:x:65534:65534:Nobody:/:/noshell\n") % sandboxUid % sandboxGid).str());
/* Declare the build user's group so that programs get a consistent /* Declare the build user's group so that programs get a consistent
view of the system (e.g., "id -gn"). */ view of the system (e.g., "id -gn"). */
writeFile(chrootRootDir + "/etc/group", writeFile(chrootRootDir + "/etc/group",
(format( "root:x:0:\n"
"root:x:0:\n" "nobody:x:65534:\n");
"nixbld:!:%1%:\n"
"nogroup:x:65534:\n") % sandboxGid).str());
/* Create /etc/hosts with localhost entry. */ /* Create /etc/hosts with localhost entry. */
if (!fixedOutput) if (!fixedOutput)
@ -2160,12 +2202,7 @@ void DerivationGoal::startBuilder()
Pid helper = startProcess([&]() { Pid helper = startProcess([&]() {
/* Drop additional groups here because we can't do it /* Drop additional groups here because we can't do it
after we've created the new user namespace. FIXME: after we've created the new user namespace. */
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)?)*/
if (getuid() == 0 && setgroups(0, 0) == -1) if (getuid() == 0 && setgroups(0, 0) == -1)
throw SysError("setgroups failed"); throw SysError("setgroups failed");
@ -2198,19 +2235,19 @@ void DerivationGoal::startBuilder()
if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort(); if (!string2Int<pid_t>(readLine(builderOut.readSide.get()), tmp)) abort();
pid = tmp; pid = tmp;
/* Set the UID/GID mapping of the builder's user namespace /* Set the UID/GID mapping of the builder's user
such that the sandbox user maps to the build user, or to namespace such that root maps to the build user, or to the
the calling user (if build users are disabled). */ calling user (if build users are disabled). */
uid_t hostUid = buildUser.enabled() ? buildUser.getUID() : getuid(); uid_t targetUid = buildUser.enabled() ? buildUser.getUID() : getuid();
uid_t hostGid = buildUser.enabled() ? buildUser.getGID() : getgid(); uid_t targetGid = buildUser.enabled() ? buildUser.getGID() : getgid();
writeFile("/proc/" + std::to_string(pid) + "/uid_map", 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) + "/setgroups", "deny");
writeFile("/proc/" + std::to_string(pid) + "/gid_map", 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 /* Signal the builder that we've updated its user
namespace. */ namespace. */
@ -2420,12 +2457,11 @@ void DerivationGoal::runChild()
if (rmdir("real-root") == -1) if (rmdir("real-root") == -1)
throw SysError("cannot remove real-root directory"); throw SysError("cannot remove real-root directory");
/* Switch to the sandbox uid/gid in the user namespace, /* Become root in the user namespace, which corresponds to
which corresponds to the build user or calling user in the build user or calling user in the parent namespace. */
the parent namespace. */ if (setgid(0) == -1)
if (setgid(sandboxGid) == -1)
throw SysError("setgid failed"); throw SysError("setgid failed");
if (setuid(sandboxUid) == -1) if (setuid(0) == -1)
throw SysError("setuid failed"); throw SysError("setuid failed");
setUser = false; setUser = false;

View file

@ -18,6 +18,10 @@ ifeq ($(OS), SunOS)
libstore_LDFLAGS += -lsocket libstore_LDFLAGS += -lsocket
endif endif
ifeq ($(OS), Linux)
libstore_LDFLAGS += -lseccomp
endif
libstore_CXXFLAGS = \ libstore_CXXFLAGS = \
-DNIX_PREFIX=\"$(prefix)\" \ -DNIX_PREFIX=\"$(prefix)\" \
-DNIX_STORE_DIR=\"$(storedir)\" \ -DNIX_STORE_DIR=\"$(storedir)\" \

54
tests/sandbox.nix Normal file
View file

@ -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"}");
'';
}