diff --git a/lix/libstore/build/local-derivation-goal.cc b/lix/libstore/build/local-derivation-goal.cc index 85c410cb8..8c5e4edd4 100644 --- a/lix/libstore/build/local-derivation-goal.cc +++ b/lix/libstore/build/local-derivation-goal.cc @@ -64,11 +64,6 @@ #include #endif -#if __APPLE__ -/* This definition is undocumented but depended upon by all major browsers. */ -extern "C" int sandbox_init_with_parameters(const char *profile, uint64_t flags, const char *const parameters[], char **errorbuf); -#endif - #include #include #include @@ -1202,156 +1197,11 @@ void LocalDerivationGoal::runChild() throw SysError("setuid failed"); } + finishChildSetup(); + /* Fill in the arguments. */ Strings args; -#if __APPLE__ - /* This has to appear before import statements. */ - std::string sandboxProfile = "(version 1)\n"; - - if (useChroot) { - - /* 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 : pathsInChroot) { - 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 pathsInChroot. We include the innermost - path component this time, since it's typically /nix/store and we care about that. */ - Path cur = worker.store.config().storeDir; - while (cur.compare("/") != 0) { - ancestry.insert(cur); - cur = dirOf(cur); - } - - /* Add all our input paths to the chroot */ - for (auto & i : inputPaths) { - auto p = worker.store.printStorePath(i); - pathsInChroot[p] = p; - } - - /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear - * to be configurable */ - if (settings.darwinLogSandboxViolations) { - sandboxProfile += "(deny default)\n"; - } else { - sandboxProfile += "(deny default (with no-log))\n"; - } - - sandboxProfile += -#include "sandbox-defaults.sb" - ; - - if (!derivationType->isSandboxed()) { - sandboxProfile += -#include "sandbox-network.sb" - ; - } - - /* Add the output paths we'll use at build-time to the chroot */ - sandboxProfile += "(allow file-read* file-write* process-exec\n"; - for (auto & [_, path] : scratchOutputs) { - sandboxProfile += fmt("\t(subpath \"%s\")\n", worker.store.printStorePath(path)); - } - - sandboxProfile += ")\n"; - - /* Our inputs (transitive dependencies and any impurities computed above) - - without file-write* allowed, access() incorrectly returns EPERM - */ - sandboxProfile += "(allow file-read* file-write* process-exec\n"; - - // We create multiple allow lists, to avoid exceeding a limit in the darwin sandbox interpreter. - // See https://github.com/NixOS/nix/issues/4119 - // We split our allow groups approximately at half the actual limit, 1 << 16 - const size_t breakpoint = sandboxProfile.length() + (1 << 14); - for (auto & i : pathsInChroot) { - if (sandboxProfile.length() >= breakpoint) { - debug("Sandbox break: %d %d", sandboxProfile.length(), breakpoint); - sandboxProfile += ")\n(allow file-read* file-write* process-exec\n"; - } - if (i.first != i.second.source) { - throw Error( - "can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin", - i.first, - i.second.source - ); - } - - std::string path = i.first; - struct stat st; - if (lstat(path.c_str(), &st)) { - if (i.second.optional && errno == ENOENT) { - continue; - } - throw SysError("getting attributes of path '%s", path); - } - if (S_ISDIR(st.st_mode)) { - sandboxProfile += fmt("\t(subpath \"%s\")\n", path); - } else { - sandboxProfile += fmt("\t(literal \"%s\")\n", path); - } - } - sandboxProfile += ")\n"; - - /* Allow file-read* on full directory hierarchy to self. Allows realpath() */ - sandboxProfile += "(allow file-read*\n"; - for (auto & i : ancestry) { - sandboxProfile += fmt("\t(literal \"%s\")\n", i); - } - sandboxProfile += ")\n"; - - sandboxProfile += additionalSandboxProfile; - } else { - sandboxProfile += -#include "sandbox-minimal.sb" - ; - } - - debug("Generated sandbox profile: %1%", sandboxProfile); - - bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking"); - - /* 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 put their files, if needed. */ - Path globalTmpDir = canonPath(defaultTempDir(), true); - - /* They don't like trailing slashes on subpath directives */ - if (globalTmpDir.back() == '/') { - globalTmpDir.pop_back(); - } - - if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") { - Strings sandboxArgs; - sandboxArgs.push_back("_NIX_BUILD_TOP"); - sandboxArgs.push_back(tmpDir); - sandboxArgs.push_back("_GLOBAL_TMP_DIR"); - sandboxArgs.push_back(globalTmpDir); - if (allowLocalNetworking) { - sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING"); - sandboxArgs.push_back("1"); - } - if (sandbox_init_with_parameters( - sandboxProfile.c_str(), 0, stringsToCharPtrs(sandboxArgs).data(), nullptr - )) - { - writeFull(STDERR_FILENO, "failed to configure sandbox\n"); - _exit(1); - } - } -#endif - args.push_back(std::string(baseNameOf(drv->builder))); for (auto & i : drv->args) diff --git a/lix/libstore/build/local-derivation-goal.hh b/lix/libstore/build/local-derivation-goal.hh index 76fbfc239..9c27bb1a7 100644 --- a/lix/libstore/build/local-derivation-goal.hh +++ b/lix/libstore/build/local-derivation-goal.hh @@ -334,6 +334,11 @@ protected: return true; } + /** + * Finish sandbox setup and prepare for actually executing the builder processes. + */ + virtual void finishChildSetup() {} + /** * Create a special accessor that can access paths that were built within the sandbox's * chroot. diff --git a/lix/libstore/platform/darwin.cc b/lix/libstore/platform/darwin.cc index 054132694..dacf73023 100644 --- a/lix/libstore/platform/darwin.cc +++ b/lix/libstore/platform/darwin.cc @@ -1,9 +1,11 @@ #include "lix/libstore/gc-store.hh" +#include "lix/libstore/build/worker.hh" #include "lix/libutil/result.hh" #include "lix/libutil/signals.hh" #include "lix/libstore/platform/darwin.hh" #include "lix/libutil/regex.hh" #include "lix/libutil/strings.hh" +#include "temporary-dir.hh" #include #include @@ -13,6 +15,11 @@ #include #include +/* This definition is undocumented but depended upon by all major browsers. */ +extern "C" int sandbox_init_with_parameters( + const char * profile, uint64_t flags, const char * const parameters[], char ** errorbuf +); + namespace nix { kj::Promise> DarwinLocalStore::findPlatformRoots(UncheckedRoots & unchecked) @@ -247,6 +254,154 @@ try { co_return result::current_exception(); } +void DarwinLocalDerivationGoal::finishChildSetup() +{ + /* This has to appear before import statements. */ + std::string sandboxProfile = "(version 1)\n"; + + if (useChroot) { + + /* 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 : pathsInChroot) { + 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 pathsInChroot. We include the innermost + path component this time, since it's typically /nix/store and we care about that. */ + Path cur = worker.store.config().storeDir; + while (cur.compare("/") != 0) { + ancestry.insert(cur); + cur = dirOf(cur); + } + + /* Add all our input paths to the chroot */ + for (auto & i : inputPaths) { + auto p = worker.store.printStorePath(i); + pathsInChroot[p] = p; + } + + /* Violations will go to the syslog if you set this. Unfortunately the destination does not appear + * to be configurable */ + if (settings.darwinLogSandboxViolations) { + sandboxProfile += "(deny default)\n"; + } else { + sandboxProfile += "(deny default (with no-log))\n"; + } + + sandboxProfile += +#include "sandbox-defaults.sb" + ; + + if (!derivationType->isSandboxed()) { + sandboxProfile += +#include "sandbox-network.sb" + ; + } + + /* Add the output paths we'll use at build-time to the chroot */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + for (auto & [_, path] : scratchOutputs) { + sandboxProfile += fmt("\t(subpath \"%s\")\n", worker.store.printStorePath(path)); + } + + sandboxProfile += ")\n"; + + /* Our inputs (transitive dependencies and any impurities computed above) + + without file-write* allowed, access() incorrectly returns EPERM + */ + sandboxProfile += "(allow file-read* file-write* process-exec\n"; + + // We create multiple allow lists, to avoid exceeding a limit in the darwin sandbox interpreter. + // See https://github.com/NixOS/nix/issues/4119 + // We split our allow groups approximately at half the actual limit, 1 << 16 + const size_t breakpoint = sandboxProfile.length() + (1 << 14); + for (auto & i : pathsInChroot) { + if (sandboxProfile.length() >= breakpoint) { + debug("Sandbox break: %d %d", sandboxProfile.length(), breakpoint); + sandboxProfile += ")\n(allow file-read* file-write* process-exec\n"; + } + if (i.first != i.second.source) { + throw Error( + "can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin", + i.first, + i.second.source + ); + } + + std::string path = i.first; + struct stat st; + if (lstat(path.c_str(), &st)) { + if (i.second.optional && errno == ENOENT) { + continue; + } + throw SysError("getting attributes of path '%s", path); + } + if (S_ISDIR(st.st_mode)) { + sandboxProfile += fmt("\t(subpath \"%s\")\n", path); + } else { + sandboxProfile += fmt("\t(literal \"%s\")\n", path); + } + } + sandboxProfile += ")\n"; + + /* Allow file-read* on full directory hierarchy to self. Allows realpath() */ + sandboxProfile += "(allow file-read*\n"; + for (auto & i : ancestry) { + sandboxProfile += fmt("\t(literal \"%s\")\n", i); + } + sandboxProfile += ")\n"; + + sandboxProfile += additionalSandboxProfile; + } else { + sandboxProfile += +#include "sandbox-minimal.sb" + ; + } + + debug("Generated sandbox profile: %1%", sandboxProfile); + + bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking"); + + /* 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 put their files, if needed. */ + Path globalTmpDir = canonPath(defaultTempDir(), true); + + /* They don't like trailing slashes on subpath directives */ + if (globalTmpDir.back() == '/') { + globalTmpDir.pop_back(); + } + + if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") { + Strings sandboxArgs; + sandboxArgs.push_back("_NIX_BUILD_TOP"); + sandboxArgs.push_back(tmpDir); + sandboxArgs.push_back("_GLOBAL_TMP_DIR"); + sandboxArgs.push_back(globalTmpDir); + if (allowLocalNetworking) { + sandboxArgs.push_back("_ALLOW_LOCAL_NETWORKING"); + sandboxArgs.push_back("1"); + } + if (sandbox_init_with_parameters( + sandboxProfile.c_str(), 0, stringsToCharPtrs(sandboxArgs).data(), nullptr + )) + { + writeFull(STDERR_FILENO, "failed to configure sandbox\n"); + _exit(1); + } + } +} + void DarwinLocalDerivationGoal::execBuilder(std::string builder, Strings args, Strings envStrs) { posix_spawnattr_t attrp; diff --git a/lix/libstore/platform/darwin.hh b/lix/libstore/platform/darwin.hh index 7a2682c37..eeffe7dc4 100644 --- a/lix/libstore/platform/darwin.hh +++ b/lix/libstore/platform/darwin.hh @@ -43,6 +43,8 @@ private: */ void prepareSandbox() override{}; + void finishChildSetup() override; + /** * Set process flags to enter or leave rosetta, then execute the builder */ diff --git a/lix/libstore/build/sandbox-defaults.sb b/lix/libstore/platform/sandbox-defaults.sb similarity index 100% rename from lix/libstore/build/sandbox-defaults.sb rename to lix/libstore/platform/sandbox-defaults.sb diff --git a/lix/libstore/build/sandbox-minimal.sb b/lix/libstore/platform/sandbox-minimal.sb similarity index 100% rename from lix/libstore/build/sandbox-minimal.sb rename to lix/libstore/platform/sandbox-minimal.sb diff --git a/lix/libstore/build/sandbox-network.sb b/lix/libstore/platform/sandbox-network.sb similarity index 100% rename from lix/libstore/build/sandbox-network.sb rename to lix/libstore/platform/sandbox-network.sb