aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/libstore/build.cc186
1 files changed, 169 insertions, 17 deletions
diff --git a/src/libstore/build.cc b/src/libstore/build.cc
index 5817611d4..2bd0d2030 100644
--- a/src/libstore/build.cc
+++ b/src/libstore/build.cc
@@ -50,6 +50,15 @@
#define CHROOT_ENABLED HAVE_CHROOT && HAVE_UNSHARE && HAVE_SYS_MOUNT_H && defined(MS_BIND) && defined(MS_PRIVATE) && defined(CLONE_NEWNS)
+/* chroot-like behavior from Apple's sandbox */
+#if __APPLE__
+ #define SANDBOX_ENABLED 1
+ #define DEFAULT_ALLOWED_IMPURE_PREFIXES "/System/Library/Frameworks /usr/lib /dev /bin/sh"
+#else
+ #define SANDBOX_ENABLED 0
+ #define DEFAULT_ALLOWED_IMPURE_PREFIXES ""
+#endif
+
#if CHROOT_ENABLED
#include <sys/socket.h>
#include <sys/ioctl.h>
@@ -1752,6 +1761,44 @@ void DerivationGoal::startBuilder()
if (get(drv.env, "__noChroot") == "1") useChroot = false;
if (useChroot) {
+ /* Allow a user-configurable set of directories from the
+ host file system. */
+ PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS)));
+ PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string("")));
+ dirs.insert(dirs2.begin(), dirs2.end());
+
+ for (auto & i : dirs) {
+ size_t p = i.find('=');
+ if (p == string::npos)
+ dirsInChroot[i] = i;
+ else
+ dirsInChroot[string(i, 0, p)] = string(i, p + 1);
+ }
+ dirsInChroot[tmpDir] = tmpDir;
+
+ string allowed = settings.get("allowed-impure-host-deps", string(DEFAULT_ALLOWED_IMPURE_PREFIXES));
+ PathSet allowedPaths = tokenizeString<StringSet>(allowed);
+
+ /* This works like the above, except on a per-derivation level */
+ Strings impurePaths = tokenizeString<Strings>(get(drv.env, "__impureHostDeps"));
+
+ for (auto & i : impurePaths) {
+ bool found = false;
+ Path canonI = canonPath(i, true);
+ /* If only we had a trie to do this more efficiently :) luckily, these are generally going to be pretty small */
+ for (auto & a : allowedPaths) {
+ Path canonA = canonPath(a, true);
+ if (canonI == canonA || isInDir(canonI, canonA)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ throw SysError(format("derivation '%1%' requested impure path ‘%2%’, but it was not in allowed-impure-host-deps (‘%3%’)") % drvPath % i % allowed);
+
+ dirsInChroot[i] = i;
+ }
+
#if CHROOT_ENABLED
/* Create a temporary directory in which we set up the chroot
environment using bind-mounts. We put it in the Nix store
@@ -1793,20 +1840,6 @@ void DerivationGoal::startBuilder()
/* Create /etc/hosts with localhost entry. */
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n");
- /* Bind-mount a user-configurable set of directories from the
- host file system. */
- PathSet dirs = tokenizeString<StringSet>(settings.get("build-chroot-dirs", string(DEFAULT_CHROOT_DIRS)));
- PathSet dirs2 = tokenizeString<StringSet>(settings.get("build-extra-chroot-dirs", string("")));
- dirs.insert(dirs2.begin(), dirs2.end());
- for (auto & i : dirs) {
- size_t p = i.find('=');
- if (p == string::npos)
- dirsInChroot[i] = i;
- else
- dirsInChroot[string(i, 0, p)] = string(i, p + 1);
- }
- dirsInChroot[tmpDir] = tmpDir;
-
/* Make the closure of the inputs available in the chroot,
rather than the whole Nix store. This prevents any access
to undeclared dependencies. Directories are bind-mounted,
@@ -1850,6 +1883,9 @@ void DerivationGoal::startBuilder()
foreach (DerivationOutputs::iterator, i, drv.outputs)
dirsInChroot.erase(i->second.path);
+#elif SANDBOX_ENABLED
+ /* We don't really have any parent prep work to do (yet?)
+ All work happens in the child, instead. */
#else
throw Error("chroot builds are not supported on this platform");
#endif
@@ -2156,8 +2192,118 @@ void DerivationGoal::runChild()
/* Fill in the arguments. */
Strings args;
- string builderBasename = baseNameOf(drv.builder);
- args.push_back(builderBasename);
+
+ const char *builder = "invalid";
+
+ string sandboxProfile;
+ if (useChroot && SANDBOX_ENABLED) {
+ /* Lots and lots and lots of file functions freak out if they can't stat their full ancestry */
+ PathSet ancestry;
+
+ /* We build the ancestry before adding all inputPaths to the store because we know they'll
+ all have the same parents (the store), and there might be lots of inputs. This isn't
+ particularly efficient... I doubt it'll be a bottleneck in practice */
+ for (auto & i : dirsInChroot) {
+ Path cur = i.first;
+ while (cur.compare("/") != 0) {
+ cur = dirOf(cur);
+ ancestry.insert(cur);
+ }
+ }
+
+ /* And we want the store in there regardless of how empty dirsInChroot. We include the innermost
+ path component this time, since it's typically /nix/store and we care about that. */
+ Path cur = settings.nixStore;
+ while (cur.compare("/") != 0) {
+ ancestry.insert(cur);
+ cur = dirOf(cur);
+ }
+
+ /* Add all our input paths to the chroot */
+ for (auto & i : inputPaths)
+ dirsInChroot[i] = i;
+
+
+ /* TODO: we should factor out the policy cleanly, so we don't have to repeat the constants every time... */
+ sandboxProfile += "(version 1)\n";
+
+ /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear to be configurable */
+ if (settings.get("darwin-log-sandbox-violations", false)) {
+ sandboxProfile += "(deny default)\n";
+ } else {
+ sandboxProfile += "(deny default (with no-log))\n";
+ }
+
+ sandboxProfile += "(allow file-read* file-write-data (literal \"/dev/null\"))\n";
+
+ sandboxProfile += "(allow file-read-metadata\n"
+ "\t(literal \"/var\")\n"
+ "\t(literal \"/tmp\")\n"
+ "\t(literal \"/etc\")\n"
+ "\t(literal \"/etc/nix\")\n"
+ "\t(literal \"/etc/nix/nix.conf\"))\n";
+
+ /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
+ to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */
+ Path globalTmpDir = canonPath(getEnv("TMPDIR", "/tmp"), true);
+
+ /* They don't like trailing slashes on subpath directives */
+ if (globalTmpDir.back() == '/') globalTmpDir.pop_back();
+
+ /* This is where our temp folders are and where most of the building will happen, so we want rwx on it. */
+ sandboxProfile += (format("(allow file-read* file-write* process-exec (subpath \"%1%\") (subpath \"/private/tmp\"))\n") % globalTmpDir).str();
+
+ sandboxProfile += "(allow process-fork)\n";
+ sandboxProfile += "(allow sysctl-read)\n";
+ sandboxProfile += "(allow signal (target same-sandbox))\n";
+
+ /* Enables getpwuid (used by git and others) */
+ sandboxProfile += "(allow mach-lookup (global-name \"com.apple.system.notification_center\") (global-name \"com.apple.system.opendirectoryd.libinfo\"))\n";
+
+
+ /* Our rwx outputs */
+ sandboxProfile += "(allow file-read* file-write* process-exec\n";
+ for (auto & i : missingPaths) {
+ sandboxProfile += (format("\t(subpath \"%1%\")\n") % i.c_str()).str();
+ }
+ sandboxProfile += ")\n";
+
+ /* Our inputs (transitive dependencies and any impurities computed above) */
+ sandboxProfile += "(allow file-read* process-exec\n";
+ for (auto & i : dirsInChroot) {
+ if (i.first != i.second)
+ throw SysError(format("can't map '%1%' to '%2%': mismatched impure paths not supported on darwin"));
+
+ string path = i.first;
+ struct stat st;
+ if (lstat(path.c_str(), &st))
+ throw SysError(format("getting attributes of path ‘%1%’") % path);
+ if (S_ISDIR(st.st_mode))
+ sandboxProfile += (format("\t(subpath \"%1%\")\n") % path).str();
+ else
+ sandboxProfile += (format("\t(literal \"%1%\")\n") % path).str();
+ }
+ sandboxProfile += ")\n";
+
+ /* Our ancestry. N.B: this uses literal on folders, instead of subpath. Without that,
+ you open up the entire filesystem because you end up with (subpath "/") */
+ sandboxProfile += "(allow file-read-metadata\n";
+ for (auto & i : ancestry) {
+ sandboxProfile += (format("\t(literal \"%1%\")\n") % i.c_str()).str();
+ }
+ sandboxProfile += ")\n";
+
+ builder = "/usr/bin/sandbox-exec";
+ args.push_back("sandbox-exec");
+ args.push_back("-p");
+ args.push_back(sandboxProfile);
+ args.push_back(drv.builder);
+ } else {
+ builder = drv.builder.c_str();
+ string builderBasename = baseNameOf(drv.builder);
+ args.push_back(builderBasename);
+ }
+
foreach (Strings::iterator, i, drv.args)
args.push_back(rewriteHashes(*i, rewritesToTmp));
auto argArr = stringsToCharPtrs(args);
@@ -2167,8 +2313,14 @@ void DerivationGoal::runChild()
/* Indicate that we managed to set up the build environment. */
writeFull(STDERR_FILENO, "\n");
+ /* This needs to be after that fateful '\n', and I didn't want to duplicate code */
+ if (useChroot && SANDBOX_ENABLED) {
+ printMsg(lvlDebug, "Generated sandbox profile:");
+ printMsg(lvlDebug, sandboxProfile);
+ }
+
/* Execute the program. This should not return. */
- execve(drv.builder.c_str(), (char * *) &argArr[0], (char * *) &envArr[0]);
+ execve(builder, (char * *) &argArr[0], (char * *) &envArr[0]);
throw SysError(format("executing ‘%1%’") % drv.builder);