aboutsummaryrefslogtreecommitdiff
path: root/src/libstore/build
diff options
context:
space:
mode:
Diffstat (limited to 'src/libstore/build')
-rw-r--r--src/libstore/build/derivation-goal.cc105
-rw-r--r--src/libstore/build/derivation-goal.hh14
-rw-r--r--src/libstore/build/entry-points.cc16
-rw-r--r--src/libstore/build/local-derivation-goal.cc250
-rw-r--r--src/libstore/build/personality.cc44
-rw-r--r--src/libstore/build/personality.hh11
-rw-r--r--src/libstore/build/sandbox-defaults.sb110
-rw-r--r--src/libstore/build/sandbox-minimal.sb9
-rw-r--r--src/libstore/build/sandbox-network.sb24
-rw-r--r--src/libstore/build/worker.cc8
-rw-r--r--src/libstore/build/worker.hh6
11 files changed, 342 insertions, 255 deletions
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 5aed51bcd..2021d0023 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -39,7 +39,6 @@
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/ip.h>
-#include <sys/personality.h>
#include <sys/mman.h>
#include <sched.h>
#include <sys/param.h>
@@ -64,7 +63,7 @@
namespace nix {
DerivationGoal::DerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
+ const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
, useDerivation(true)
, drvPath(drvPath)
@@ -83,7 +82,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
+ const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
, useDerivation(false)
, drvPath(drvPath)
@@ -134,7 +133,7 @@ void DerivationGoal::killChild()
void DerivationGoal::timedOut(Error && ex)
{
killChild();
- done(BuildResult::TimedOut, {}, ex);
+ done(BuildResult::TimedOut, {}, std::move(ex));
}
@@ -143,18 +142,12 @@ void DerivationGoal::work()
(this->*state)();
}
-void DerivationGoal::addWantedOutputs(const StringSet & outputs)
+void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
{
- /* If we already want all outputs, there is nothing to do. */
- if (wantedOutputs.empty()) return;
-
- if (outputs.empty()) {
- wantedOutputs.clear();
+ auto newWanted = wantedOutputs.union_(outputs);
+ if (!newWanted.isSubsetOf(wantedOutputs))
needRestart = true;
- } else
- for (auto & i : outputs)
- if (wantedOutputs.insert(i).second)
- needRestart = true;
+ wantedOutputs = newWanted;
}
@@ -391,7 +384,7 @@ void DerivationGoal::repairClosure()
auto outputs = queryDerivationOutputMap();
StorePathSet outputClosure;
for (auto & i : outputs) {
- if (!wantOutput(i.first, wantedOutputs)) continue;
+ if (!wantedOutputs.contains(i.first)) continue;
worker.store.computeFSClosure(i.second, outputClosure);
}
@@ -423,7 +416,7 @@ void DerivationGoal::repairClosure()
if (drvPath2 == outputsToDrv.end())
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
else
- addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair));
+ addWaitee(worker.makeDerivationGoal(drvPath2->second, OutputsSpec::All(), bmRepair));
}
if (waitees.empty()) {
@@ -545,7 +538,8 @@ void DerivationGoal::inputsRealised()
However, the impure derivations feature still relies on this
fragile way of doing things, because its builds do not have
a representation in the store, which is a usability problem
- in itself */
+ in itself. When implementing this logic entirely with lookups
+ make sure that they're cached. */
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
worker.store.computeFSClosure(*outPath, inputPaths);
}
@@ -571,10 +565,6 @@ void DerivationGoal::inputsRealised()
/* What type of derivation are we building? */
derivationType = drv->type();
- /* Don't repeat fixed-output derivations since they're already
- verified by their output hash.*/
- nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1;
-
/* Okay, try to build. Note that here we don't wait for a build
slot to become available, since we don't need one if there is a
build hook. */
@@ -589,12 +579,11 @@ void DerivationGoal::started()
auto msg = fmt(
buildMode == bmRepair ? "repairing outputs of '%s'" :
buildMode == bmCheck ? "checking outputs of '%s'" :
- nrRounds > 1 ? "building '%s' (round %d/%d)" :
- "building '%s'", worker.store.printStorePath(drvPath), curRound, nrRounds);
+ "building '%s'", worker.store.printStorePath(drvPath));
fmt("building '%s'", worker.store.printStorePath(drvPath));
if (hook) msg += fmt(" on '%s'", machineName);
act = std::make_unique<Activity>(*logger, lvlInfo, actBuild, msg,
- Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", curRound, nrRounds});
+ Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", 1, 1});
mcRunningBuilds = std::make_unique<MaintainCount<uint64_t>>(worker.runningBuilds);
worker.updateProgress();
}
@@ -948,14 +937,6 @@ void DerivationGoal::buildDone()
cleanupPostOutputsRegisteredModeNonCheck();
- /* Repeat the build if necessary. */
- if (curRound++ < nrRounds) {
- outputLocks.unlock();
- state = &DerivationGoal::tryToBuild;
- worker.wakeUp(shared_from_this());
- return;
- }
-
/* It is now safe to delete the lock files, since all future
lockers will see that the output paths are valid; they will
not create new lock files with the same names as the old
@@ -984,7 +965,7 @@ void DerivationGoal::buildDone()
BuildResult::PermanentFailure;
}
- done(st, {}, e);
+ done(st, {}, std::move(e));
return;
}
}
@@ -1004,10 +985,15 @@ void DerivationGoal::resolvedFinished()
StorePathSet outputPaths;
- // `wantedOutputs` might be empty, which means “all the outputs”
- auto realWantedOutputs = wantedOutputs;
- if (realWantedOutputs.empty())
- realWantedOutputs = resolvedDrv.outputNames();
+ // `wantedOutputs` might merely indicate “all the outputs”
+ auto realWantedOutputs = std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return resolvedDrv.outputNames();
+ },
+ [&](const OutputsSpec::Names & names) {
+ return static_cast<std::set<std::string>>(names);
+ },
+ }, wantedOutputs.raw());
for (auto & wantedOutput : realWantedOutputs) {
auto initialOutput = get(initialOutputs, wantedOutput);
@@ -1016,22 +1002,34 @@ void DerivationGoal::resolvedFinished()
throw Error(
"derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)",
worker.store.printStorePath(drvPath), wantedOutput);
- auto realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput });
- if (!realisation)
- throw Error(
- "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)",
- worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput);
+
+ auto realisation = [&]{
+ auto take1 = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput });
+ if (take1) return *take1;
+
+ /* The above `get` should work. But sateful tracking of
+ outputs in resolvedResult, this can get out of sync with the
+ store, which is our actual source of truth. For now we just
+ check the store directly if it fails. */
+ auto take2 = worker.evalStore.queryRealisation(DrvOutput { *resolvedHash, wantedOutput });
+ if (take2) return *take2;
+
+ throw Error(
+ "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)",
+ worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput);
+ }();
+
if (drv->type().isPure()) {
- auto newRealisation = *realisation;
+ auto newRealisation = realisation;
newRealisation.id = DrvOutput { initialOutput->outputHash, wantedOutput };
newRealisation.signatures.clear();
if (!drv->type().isFixed())
- newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
+ newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath);
signRealisation(newRealisation);
worker.store.registerDrvOutput(newRealisation);
}
- outputPaths.insert(realisation->outPath);
- builtOutputs.emplace(realisation->id, *realisation);
+ outputPaths.insert(realisation.outPath);
+ builtOutputs.emplace(realisation.id, realisation);
}
runPostBuildHook(
@@ -1323,7 +1321,14 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
if (!drv->type().isPure()) return { false, {} };
bool checkHash = buildMode == bmRepair;
- auto wantedOutputsLeft = wantedOutputs;
+ auto wantedOutputsLeft = std::visit(overloaded {
+ [&](const OutputsSpec::All &) {
+ return StringSet {};
+ },
+ [&](const OutputsSpec::Names & names) {
+ return static_cast<StringSet>(names);
+ },
+ }, wantedOutputs.raw());
DrvOutputs validOutputs;
for (auto & i : queryPartialDerivationOutputMap()) {
@@ -1332,7 +1337,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
continue;
auto & info = *initialOutput;
- info.wanted = wantOutput(i.first, wantedOutputs);
+ info.wanted = wantedOutputs.contains(i.first);
if (info.wanted)
wantedOutputsLeft.erase(i.first);
if (i.second) {
@@ -1370,7 +1375,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
}
- // If we requested all the outputs via the empty set, we are always fine.
+ // If we requested all the outputs, we are always fine.
// If we requested specific elements, the loop above removes all the valid
// ones, so any that are left must be invalid.
if (!wantedOutputsLeft.empty())
@@ -1435,7 +1440,7 @@ void DerivationGoal::done(
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
}
- amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
+ amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex));
}
diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh
index 2d8bfd592..707e38b4b 100644
--- a/src/libstore/build/derivation-goal.hh
+++ b/src/libstore/build/derivation-goal.hh
@@ -2,6 +2,7 @@
#include "parsed-derivations.hh"
#include "lock.hh"
+#include "outputs-spec.hh"
#include "store-api.hh"
#include "pathlocks.hh"
#include "goal.hh"
@@ -55,7 +56,7 @@ struct DerivationGoal : public Goal
/* The specific outputs that we need to build. Empty means all of
them. */
- StringSet wantedOutputs;
+ OutputsSpec wantedOutputs;
/* Mapping from input derivations + output names to actual store
paths. This is filled in by waiteeDone() as each dependency
@@ -115,11 +116,6 @@ struct DerivationGoal : public Goal
BuildMode buildMode;
- /* The current round, if we're building multiple times. */
- size_t curRound = 1;
-
- size_t nrRounds;
-
std::unique_ptr<MaintainCount<uint64_t>> mcExpectedBuilds, mcRunningBuilds;
std::unique_ptr<Activity> act;
@@ -133,10 +129,10 @@ struct DerivationGoal : public Goal
std::string machineName;
DerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, Worker & worker,
+ const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, Worker & worker,
+ const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal);
virtual ~DerivationGoal();
@@ -147,7 +143,7 @@ struct DerivationGoal : public Goal
void work() override;
/* Add wanted outputs to an already existing derivation goal. */
- void addWantedOutputs(const StringSet & outputs);
+ void addWantedOutputs(const OutputsSpec & outputs);
/* The states. */
void getDerivation();
diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc
index bea7363db..2925fe3ca 100644
--- a/src/libstore/build/entry-points.cc
+++ b/src/libstore/build/entry-points.cc
@@ -30,7 +30,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
if (ex)
logError(i->ex->info());
else
- ex = i->ex;
+ ex = std::move(i->ex);
}
if (i->exitCode != Goal::ecSuccess) {
if (auto i2 = dynamic_cast<DerivationGoal *>(i.get())) failed.insert(i2->drvPath);
@@ -40,7 +40,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
if (failed.size() == 1 && ex) {
ex->status = worker.exitStatus();
- throw *ex;
+ throw std::move(*ex);
} else if (!failed.empty()) {
if (ex) logError(ex->info());
throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed));
@@ -80,7 +80,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
BuildMode buildMode)
{
Worker worker(*this, *this);
- auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
+ auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
try {
worker.run(Goals{goal});
@@ -89,7 +89,10 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
return BuildResult {
.status = BuildResult::MiscFailure,
.errorMsg = e.msg(),
- .path = DerivedPath::Built { .drvPath = drvPath },
+ .path = DerivedPath::Built {
+ .drvPath = drvPath,
+ .outputs = OutputsSpec::All { },
+ },
};
};
}
@@ -109,7 +112,7 @@ void Store::ensurePath(const StorePath & path)
if (goal->exitCode != Goal::ecSuccess) {
if (goal->ex) {
goal->ex->status = worker.exitStatus();
- throw *goal->ex;
+ throw std::move(*goal->ex);
} else
throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path));
}
@@ -130,7 +133,8 @@ void LocalStore::repairPath(const StorePath & path)
auto info = queryPathInfo(path);
if (info->deriver && isValidPath(*info->deriver)) {
goals.clear();
- goals.insert(worker.makeDerivationGoal(*info->deriver, StringSet(), bmRepair));
+ // FIXME: Should just build the specific output we need.
+ goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
worker.run(goals);
} else
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index d2798888b..a961d8eed 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -15,6 +15,8 @@
#include "callback.hh"
#include "json-utils.hh"
#include "cgroup.hh"
+#include "personality.hh"
+#include "namespaces.hh"
#include <regex>
#include <queue>
@@ -24,7 +26,6 @@
#include <termios.h>
#include <unistd.h>
#include <sys/mman.h>
-#include <sys/utsname.h>
#include <sys/resource.h>
#include <sys/socket.h>
@@ -37,7 +38,6 @@
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/ip.h>
-#include <sys/personality.h>
#include <sys/mman.h>
#include <sched.h>
#include <sys/param.h>
@@ -168,7 +168,8 @@ void LocalDerivationGoal::killSandbox(bool getStats)
}
-void LocalDerivationGoal::tryLocalBuild() {
+void LocalDerivationGoal::tryLocalBuild()
+{
unsigned int curBuilds = worker.getNrLocalBuilds();
if (curBuilds >= settings.maxBuildJobs) {
state = &DerivationGoal::tryToBuild;
@@ -206,6 +207,17 @@ void LocalDerivationGoal::tryLocalBuild() {
#endif
}
+ #if __linux__
+ if (useChroot) {
+ if (!mountAndPidNamespacesSupported()) {
+ if (!settings.sandboxFallback)
+ throw Error("this system does not support the kernel namespaces that are required for sandboxing; use '--no-sandbox' to disable sandboxing");
+ debug("auto-disabling sandboxing because the prerequisite namespaces are not available");
+ useChroot = false;
+ }
+ }
+ #endif
+
if (useBuildUsers()) {
if (!buildUser)
buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1, useChroot);
@@ -230,7 +242,7 @@ void LocalDerivationGoal::tryLocalBuild() {
outputLocks.unlock();
buildUser.reset();
worker.permanentFailure = true;
- done(BuildResult::InputRejected, {}, e);
+ done(BuildResult::InputRejected, {}, std::move(e));
return;
}
@@ -373,12 +385,6 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
}
-int childEntry(void * arg)
-{
- ((LocalDerivationGoal *) arg)->runChild();
- return 1;
-}
-
#if __linux__
static void linkOrCopy(const Path & from, const Path & to)
{
@@ -664,7 +670,8 @@ void LocalDerivationGoal::startBuilder()
nobody account. The latter is kind of a hack to support
Samba-in-QEMU. */
createDirs(chrootRootDir + "/etc");
- chownToBuilder(chrootRootDir + "/etc");
+ if (parsedDrv->useUidRange())
+ chownToBuilder(chrootRootDir + "/etc");
if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536))
throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name);
@@ -889,12 +896,7 @@ void LocalDerivationGoal::startBuilder()
userNamespaceSync.create();
- Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
- static bool userNamespacesEnabled =
- pathExists(maxUserNamespaces)
- && trim(readFile(maxUserNamespaces)) != "0";
-
- usingUserNamespace = userNamespacesEnabled;
+ usingUserNamespace = userNamespacesSupported();
Pid helper = startProcess([&]() {
@@ -909,76 +911,21 @@ void LocalDerivationGoal::startBuilder()
if (getuid() == 0 && setgroups(0, 0) == -1)
throw SysError("setgroups failed");
- size_t stackSize = 1 * 1024 * 1024;
- char * stack = (char *) mmap(0, stackSize,
- PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
- if (stack == MAP_FAILED) throw SysError("allocating stack");
-
- int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
+ ProcessOptions options;
+ options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD;
if (privateNetwork)
- flags |= CLONE_NEWNET;
+ options.cloneFlags |= CLONE_NEWNET;
if (usingUserNamespace)
- flags |= CLONE_NEWUSER;
-
- pid_t child = clone(childEntry, stack + stackSize, flags, this);
- if (child == -1 && errno == EINVAL) {
- /* Fallback for Linux < 2.13 where CLONE_NEWPID and
- CLONE_PARENT are not allowed together. */
- flags &= ~CLONE_NEWPID;
- child = clone(childEntry, stack + stackSize, flags, this);
- }
- if (usingUserNamespace && child == -1 && (errno == EPERM || errno == EINVAL)) {
- /* Some distros patch Linux to not allow unprivileged
- * user namespaces. If we get EPERM or EINVAL, try
- * without CLONE_NEWUSER and see if that works.
- * Details: https://salsa.debian.org/kernel-team/linux/-/commit/d98e00eda6bea437e39b9e80444eee84a32438a6
- */
- usingUserNamespace = false;
- flags &= ~CLONE_NEWUSER;
- child = clone(childEntry, stack + stackSize, flags, this);
- }
- if (child == -1) {
- switch(errno) {
- case EPERM:
- case EINVAL: {
- int errno_ = errno;
- if (!userNamespacesEnabled && errno==EPERM)
- notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/user/max_user_namespaces");
- if (userNamespacesEnabled) {
- Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
- if (pathExists(procSysKernelUnprivilegedUsernsClone)
- && trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") {
- notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/kernel/unprivileged_userns_clone");
- }
- }
- Path procSelfNsUser = "/proc/self/ns/user";
- if (!pathExists(procSelfNsUser))
- notice("/proc/self/ns/user does not exist; your kernel was likely built without CONFIG_USER_NS=y, which is required for sandboxing");
- /* Otherwise exit with EPERM so we can handle this in the
- parent. This is only done when sandbox-fallback is set
- to true (the default). */
- if (settings.sandboxFallback)
- _exit(1);
- /* Mention sandbox-fallback in the error message so the user
- knows that having it disabled contributed to the
- unrecoverability of this failure */
- throw SysError(errno_, "creating sandboxed builder process using clone(), without sandbox-fallback");
- }
- default:
- throw SysError("creating sandboxed builder process using clone()");
- }
- }
+ options.cloneFlags |= CLONE_NEWUSER;
+
+ pid_t child = startProcess([&]() { runChild(); }, options);
+
writeFull(builderOut.writeSide.get(),
fmt("%d %d\n", usingUserNamespace, child));
_exit(0);
});
- int res = helper.wait();
- if (res != 0 && settings.sandboxFallback) {
- useChroot = false;
- initTmpDir();
- goto fallback;
- } else if (res != 0)
+ if (helper.wait() != 0)
throw Error("unable to start build process");
userNamespaceSync.readSide = -1;
@@ -1046,9 +993,6 @@ void LocalDerivationGoal::startBuilder()
} else
#endif
{
-#if __linux__
- fallback:
-#endif
pid = startProcess([&]() {
runChild();
});
@@ -1460,7 +1404,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
unknown, downloadSize, narSize);
}
- virtual std::optional<std::string> getBuildLog(const StorePath & path) override
+ virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ return std::nullopt; }
virtual void addBuildLog(const StorePath & path, std::string_view log) override
@@ -1517,8 +1461,7 @@ void LocalDerivationGoal::startDaemon()
FdSink to(remote.get());
try {
daemon::processConnection(store, from, to,
- daemon::NotTrusted, daemon::Recursive,
- [&](Store & store) { store.createUser("nobody", 65535); });
+ daemon::NotTrusted, daemon::Recursive);
debug("terminated daemon connection");
} catch (SysError &) {
ignoreException();
@@ -1908,6 +1851,10 @@ void LocalDerivationGoal::runChild()
}
}
+ /* Make /etc unwritable */
+ if (!parsedDrv->useUidRange())
+ chmod_(chrootRootDir + "/etc", 0555);
+
/* Unshare this mount namespace. This is necessary because
pivot_root() below changes the root of the mount
namespace. This means that the call to setns() in
@@ -1964,33 +1911,7 @@ void LocalDerivationGoal::runChild()
/* Close all other file descriptors. */
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
-#if __linux__
- /* Change the personality to 32-bit if we're doing an
- i686-linux build on an x86_64-linux machine. */
- struct utsname utsbuf;
- uname(&utsbuf);
- if ((drv->platform == "i686-linux"
- && (settings.thisSystem == "x86_64-linux"
- || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
- || drv->platform == "armv7l-linux"
- || drv->platform == "armv6l-linux")
- {
- if (personality(PER_LINUX32) == -1)
- throw SysError("cannot set 32-bit personality");
- }
-
- /* Impersonate a Linux 2.6 machine to get some determinism in
- builds that depend on the kernel version. */
- if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) {
- int cur = personality(0xffffffff);
- if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
- }
-
- /* Disable address space randomization for improved
- determinism. */
- int cur = personality(0xffffffff);
- if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
-#endif
+ setPersonality(drv->platform);
/* Disable core dumps by default. */
struct rlimit limit = { 0, RLIM_INFINITY };
@@ -2077,10 +1998,14 @@ void LocalDerivationGoal::runChild()
sandboxProfile += "(deny default (with no-log))\n";
}
- sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
+ sandboxProfile +=
+ #include "sandbox-defaults.sb"
+ ;
if (!derivationType.isSandboxed())
- sandboxProfile += "(import \"sandbox-network.sb\")\n";
+ 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";
@@ -2123,7 +2048,9 @@ void LocalDerivationGoal::runChild()
sandboxProfile += additionalSandboxProfile;
} else
- sandboxProfile += "(import \"sandbox-minimal.sb\")\n";
+ sandboxProfile +=
+ #include "sandbox-minimal.sb"
+ ;
debug("Generated sandbox profile:");
debug(sandboxProfile);
@@ -2148,8 +2075,6 @@ void LocalDerivationGoal::runChild()
args.push_back(sandboxFile);
args.push_back("-D");
args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir);
- args.push_back("-D");
- args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/");
if (allowLocalNetworking) {
args.push_back("-D");
args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1"));
@@ -2260,7 +2185,6 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
InodesSeen inodesSeen;
Path checkSuffix = ".check";
- bool keepPreviousRound = settings.keepFailed || settings.runDiffHook;
std::exception_ptr delayedException;
@@ -2347,11 +2271,28 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
buildUser ? std::optional(buildUser->getUIDRange()) : std::nullopt,
inodesSeen);
- debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath);
+ bool discardReferences = false;
+ if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) {
+ if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) {
+ settings.requireExperimentalFeature(Xp::DiscardReferences);
+ if (auto output = get(*udr, outputName)) {
+ if (!output->is_boolean())
+ throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string());
+ discardReferences = output->get<bool>();
+ }
+ }
+ }
- /* Pass blank Sink as we are not ready to hash data at this stage. */
- NullSink blank;
- auto references = scanForReferences(blank, actualPath, referenceablePaths);
+ StorePathSet references;
+ if (discardReferences)
+ debug("discarding references of output '%s'", outputName);
+ else {
+ debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath);
+
+ /* Pass blank Sink as we are not ready to hash data at this stage. */
+ NullSink blank;
+ references = scanForReferences(blank, actualPath, referenceablePaths);
+ }
outputReferencesIfUnregistered.insert_or_assign(
outputName,
@@ -2688,10 +2629,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
debug("unreferenced input: '%1%'", worker.store.printStorePath(i));
}
- if (curRound == nrRounds) {
- localStore.optimisePath(actualPath, NoRepair); // FIXME: combine with scanForReferences()
- worker.markContentsGood(newInfo.path);
- }
+ localStore.optimisePath(actualPath, NoRepair); // FIXME: combine with scanForReferences()
+ worker.markContentsGood(newInfo.path);
newInfo.deriver = drvPath;
newInfo.ultimate = true;
@@ -2720,61 +2659,6 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
/* Apply output checks. */
checkOutputs(infos);
- /* Compare the result with the previous round, and report which
- path is different, if any.*/
- if (curRound > 1 && prevInfos != infos) {
- assert(prevInfos.size() == infos.size());
- for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
- if (!(*i == *j)) {
- buildResult.isNonDeterministic = true;
- Path prev = worker.store.printStorePath(i->second.path) + checkSuffix;
- bool prevExists = keepPreviousRound && pathExists(prev);
- hintformat hint = prevExists
- ? hintfmt("output '%s' of '%s' differs from '%s' from previous round",
- worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath), prev)
- : hintfmt("output '%s' of '%s' differs from previous round",
- worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath));
-
- handleDiffHook(
- buildUser ? buildUser->getUID() : getuid(),
- buildUser ? buildUser->getGID() : getgid(),
- prev, worker.store.printStorePath(i->second.path),
- worker.store.printStorePath(drvPath), tmpDir);
-
- if (settings.enforceDeterminism)
- throw NotDeterministic(hint);
-
- printError(hint);
-
- curRound = nrRounds; // we know enough, bail out early
- }
- }
-
- /* If this is the first round of several, then move the output out of the way. */
- if (nrRounds > 1 && curRound == 1 && curRound < nrRounds && keepPreviousRound) {
- for (auto & [_, outputStorePath] : finalOutputs) {
- auto path = worker.store.printStorePath(outputStorePath);
- Path prev = path + checkSuffix;
- deletePath(prev);
- Path dst = path + checkSuffix;
- renameFile(path, dst);
- }
- }
-
- if (curRound < nrRounds) {
- prevInfos = std::move(infos);
- return {};
- }
-
- /* Remove the .check directories if we're done. FIXME: keep them
- if the result was not determistic? */
- if (curRound == nrRounds) {
- for (auto & [_, outputStorePath] : finalOutputs) {
- Path prev = worker.store.printStorePath(outputStorePath) + checkSuffix;
- deletePath(prev);
- }
- }
-
/* Register each output path as valid, and register the sets of
paths referenced by each of them. If there are cycles in the
outputs, this will fail. */
@@ -2816,7 +2700,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
signRealisation(thisRealisation);
worker.store.registerDrvOutput(thisRealisation);
}
- if (wantOutput(outputName, wantedOutputs))
+ if (wantedOutputs.contains(outputName))
builtOutputs.emplace(thisRealisation.id, thisRealisation);
}
diff --git a/src/libstore/build/personality.cc b/src/libstore/build/personality.cc
new file mode 100644
index 000000000..4ad477869
--- /dev/null
+++ b/src/libstore/build/personality.cc
@@ -0,0 +1,44 @@
+#include "personality.hh"
+#include "globals.hh"
+
+#if __linux__
+#include <sys/utsname.h>
+#include <sys/personality.h>
+#endif
+
+#include <cstring>
+
+namespace nix {
+
+void setPersonality(std::string_view system)
+{
+#if __linux__
+ /* Change the personality to 32-bit if we're doing an
+ i686-linux build on an x86_64-linux machine. */
+ struct utsname utsbuf;
+ uname(&utsbuf);
+ if ((system == "i686-linux"
+ && (std::string_view(SYSTEM) == "x86_64-linux"
+ || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
+ || system == "armv7l-linux"
+ || system == "armv6l-linux")
+ {
+ if (personality(PER_LINUX32) == -1)
+ throw SysError("cannot set 32-bit personality");
+ }
+
+ /* Impersonate a Linux 2.6 machine to get some determinism in
+ builds that depend on the kernel version. */
+ if ((system == "i686-linux" || system == "x86_64-linux") && settings.impersonateLinux26) {
+ int cur = personality(0xffffffff);
+ if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
+ }
+
+ /* Disable address space randomization for improved
+ determinism. */
+ int cur = personality(0xffffffff);
+ if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
+#endif
+}
+
+}
diff --git a/src/libstore/build/personality.hh b/src/libstore/build/personality.hh
new file mode 100644
index 000000000..30e4f4062
--- /dev/null
+++ b/src/libstore/build/personality.hh
@@ -0,0 +1,11 @@
+#pragma once
+
+#include <string>
+
+namespace nix {
+
+void setPersonality(std::string_view system);
+
+}
+
+
diff --git a/src/libstore/build/sandbox-defaults.sb b/src/libstore/build/sandbox-defaults.sb
new file mode 100644
index 000000000..77f013aea
--- /dev/null
+++ b/src/libstore/build/sandbox-defaults.sb
@@ -0,0 +1,110 @@
+R""(
+
+(define TMPDIR (param "_GLOBAL_TMP_DIR"))
+
+(deny default)
+
+; Disallow creating setuid/setgid binaries, since that
+; would allow breaking build user isolation.
+(deny file-write-setugid)
+
+; Allow forking.
+(allow process-fork)
+
+; Allow reading system information like #CPUs, etc.
+(allow sysctl-read)
+
+; Allow POSIX semaphores and shared memory.
+(allow ipc-posix*)
+
+; Allow socket creation.
+(allow system-socket)
+
+; Allow sending signals within the sandbox.
+(allow signal (target same-sandbox))
+
+; Allow getpwuid.
+(allow mach-lookup (global-name "com.apple.system.opendirectoryd.libinfo"))
+
+; Access to /tmp.
+; The network-outbound/network-inbound ones are for unix domain sockets, which
+; we allow access to in TMPDIR (but if we allow them more broadly, you could in
+; theory escape the sandbox)
+(allow file* process-exec network-outbound network-inbound
+ (literal "/tmp") (subpath TMPDIR))
+
+; Some packages like to read the system version.
+(allow file-read*
+ (literal "/System/Library/CoreServices/SystemVersion.plist")
+ (literal "/System/Library/CoreServices/SystemVersionCompat.plist"))
+
+; Without this line clang cannot write to /dev/null, breaking some configure tests.
+(allow file-read-metadata (literal "/dev"))
+
+; Many packages like to do local networking in their test suites, but let's only
+; allow it if the package explicitly asks for it.
+(if (param "_ALLOW_LOCAL_NETWORKING")
+ (begin
+ (allow network* (local ip) (local tcp) (local udp))
+
+ ; Allow access to /etc/resolv.conf (which is a symlink to
+ ; /private/var/run/resolv.conf).
+ ; TODO: deduplicate with sandbox-network.sb
+ (allow file-read-metadata
+ (literal "/var")
+ (literal "/etc")
+ (literal "/etc/resolv.conf")
+ (literal "/private/etc/resolv.conf"))
+
+ (allow file-read*
+ (literal "/private/var/run/resolv.conf"))
+
+ ; Allow DNS lookups. This is even needed for localhost, which lots of tests rely on
+ (allow file-read-metadata (literal "/etc/hosts"))
+ (allow file-read* (literal "/private/etc/hosts"))
+ (allow network-outbound (remote unix-socket (path-literal "/private/var/run/mDNSResponder")))))
+
+; Standard devices.
+(allow file*
+ (literal "/dev/null")
+ (literal "/dev/random")
+ (literal "/dev/stdin")
+ (literal "/dev/stdout")
+ (literal "/dev/tty")
+ (literal "/dev/urandom")
+ (literal "/dev/zero")
+ (subpath "/dev/fd"))
+
+; Allow pseudo-terminals.
+(allow file*
+ (literal "/dev/ptmx")
+ (regex #"^/dev/pty[a-z]+")
+ (regex #"^/dev/ttys[0-9]+"))
+
+; Does nothing, but reduces build noise.
+(allow file* (literal "/dev/dtracehelper"))
+
+; Allow access to zoneinfo since libSystem needs it.
+(allow file-read* (subpath "/usr/share/zoneinfo"))
+
+(allow file-read* (subpath "/usr/share/locale"))
+
+; This is mostly to get more specific log messages when builds try to
+; access something in /etc or /var.
+(allow file-read-metadata
+ (literal "/etc")
+ (literal "/var")
+ (literal "/private/var/tmp"))
+
+; This is used by /bin/sh on macOS 10.15 and later.
+(allow file*
+ (literal "/private/var/select/sh"))
+
+; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin (and vice versa).
+(allow file-read*
+ (subpath "/Library/Apple/usr/libexec/oah")
+ (subpath "/System/Library/Apple/usr/libexec/oah")
+ (subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
+ (subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))
+
+)""
diff --git a/src/libstore/build/sandbox-minimal.sb b/src/libstore/build/sandbox-minimal.sb
new file mode 100644
index 000000000..976a1f636
--- /dev/null
+++ b/src/libstore/build/sandbox-minimal.sb
@@ -0,0 +1,9 @@
+R""(
+
+(allow default)
+
+; Disallow creating setuid/setgid binaries, since that
+; would allow breaking build user isolation.
+(deny file-write-setugid)
+
+)""
diff --git a/src/libstore/build/sandbox-network.sb b/src/libstore/build/sandbox-network.sb
new file mode 100644
index 000000000..335edbaed
--- /dev/null
+++ b/src/libstore/build/sandbox-network.sb
@@ -0,0 +1,24 @@
+R""(
+
+; Allow local and remote network traffic.
+(allow network* (local ip) (remote ip))
+
+; Allow access to /etc/resolv.conf (which is a symlink to
+; /private/var/run/resolv.conf).
+(allow file-read-metadata
+ (literal "/var")
+ (literal "/etc")
+ (literal "/etc/resolv.conf")
+ (literal "/private/etc/resolv.conf"))
+
+(allow file-read*
+ (literal "/private/var/run/resolv.conf"))
+
+; Allow DNS lookups.
+(allow network-outbound (remote unix-socket (path-literal "/private/var/run/mDNSResponder")))
+
+; Allow access to trustd.
+(allow mach-lookup (global-name "com.apple.trustd"))
+(allow mach-lookup (global-name "com.apple.trustd.agent"))
+
+)""
diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc
index b192fbc77..f775f8486 100644
--- a/src/libstore/build/worker.cc
+++ b/src/libstore/build/worker.cc
@@ -42,7 +42,7 @@ Worker::~Worker()
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
const StorePath & drvPath,
- const StringSet & wantedOutputs,
+ const OutputsSpec & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
{
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
@@ -59,7 +59,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
- const StringSet & wantedOutputs, BuildMode buildMode)
+ const OutputsSpec & wantedOutputs, BuildMode buildMode)
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
@@ -70,7 +70,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
- const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode)
+ const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
{
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store)
@@ -276,7 +276,7 @@ void Worker::run(const Goals & _topGoals)
if (!children.empty() || !waitingForAWhile.empty())
waitForInput();
else {
- if (awake.empty() && 0 == settings.maxBuildJobs)
+ if (awake.empty() && 0U == settings.maxBuildJobs)
{
if (getMachines().empty())
throw Error("unable to start any build; either increase '--max-jobs' "
diff --git a/src/libstore/build/worker.hh b/src/libstore/build/worker.hh
index a1e036a96..6d68d3cf1 100644
--- a/src/libstore/build/worker.hh
+++ b/src/libstore/build/worker.hh
@@ -140,15 +140,15 @@ public:
/* derivation goal */
private:
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
- const StorePath & drvPath, const StringSet & wantedOutputs,
+ const StorePath & drvPath, const OutputsSpec & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
public:
std::shared_ptr<DerivationGoal> makeDerivationGoal(
const StorePath & drvPath,
- const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+ const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
const StorePath & drvPath, const BasicDerivation & drv,
- const StringSet & wantedOutputs, BuildMode buildMode = bmNormal);
+ const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
/* substitution goal */
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);