libstore: move some macos-specific bits to platform

Change-Id: I9236ffb8e098d09215067b872b5da5e210557815
This commit is contained in:
eldritch horrors
2026-01-21 15:59:30 +01:00
parent c39488d2a4
commit 6edbef7338
7 changed files with 164 additions and 152 deletions
+2 -152
View File
@@ -64,11 +64,6 @@
#include <sys/syscall.h>
#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 <pwd.h>
#include <grp.h>
#include <iostream>
@@ -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)
@@ -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.
+155
View File
@@ -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 <sys/proc_info.h>
#include <sys/sysctl.h>
@@ -13,6 +15,11 @@
#include <cstddef>
#include <regex>
/* 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<Result<void>> 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;
+2
View File
@@ -43,6 +43,8 @@ private:
*/
void prepareSandbox() override{};
void finishChildSetup() override;
/**
* Set process flags to enter or leave rosetta, then execute the builder
*/