mirror of
https://github.com/NixOS/nix
synced 2025-06-24 22:11:15 +02:00
Eliminate useChroot
This commit is contained in:
parent
67408807d8
commit
9f8f3968e3
3 changed files with 155 additions and 101 deletions
|
@ -1,5 +1,15 @@
|
|||
#ifdef __APPLE__
|
||||
|
||||
# include <spawn.h>
|
||||
# include <sys/sysctl.h>
|
||||
# include <sandbox.h>
|
||||
|
||||
/* 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 {
|
||||
|
||||
struct DarwinDerivationBuilder : DerivationBuilderImpl
|
||||
{
|
||||
PathsInChroot pathsInChroot;
|
||||
|
@ -185,4 +195,6 @@ struct DarwinDerivationBuilder : DerivationBuilderImpl
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -52,15 +52,6 @@
|
|||
# include "nix/store/personality.hh"
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <spawn.h>
|
||||
# include <sys/sysctl.h>
|
||||
# include <sandbox.h>
|
||||
|
||||
/* 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>
|
||||
|
@ -116,6 +107,7 @@ protected:
|
|||
/**
|
||||
* The cgroup of the builder, if any.
|
||||
*/
|
||||
// FIXME: move
|
||||
std::optional<Path> cgroup;
|
||||
|
||||
/**
|
||||
|
@ -134,18 +126,6 @@ protected:
|
|||
*/
|
||||
Path tmpDirInSandbox;
|
||||
|
||||
/**
|
||||
* Whether we're currently doing a chroot build.
|
||||
*/
|
||||
// FIXME: remove
|
||||
bool useChroot = false;
|
||||
|
||||
/**
|
||||
* The root of the chroot environment.
|
||||
*/
|
||||
// FIXME: move
|
||||
Path chrootRootDir;
|
||||
|
||||
/**
|
||||
* RAII object to delete the chroot directory.
|
||||
*/
|
||||
|
@ -250,6 +230,14 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Acquire a build user lock. Return nullptr if no lock is available.
|
||||
*/
|
||||
virtual std::unique_ptr<UserLock> getBuildUser()
|
||||
{
|
||||
return acquireUserLock(1, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the paths that should be made available in the sandbox.
|
||||
* This includes:
|
||||
|
@ -261,12 +249,28 @@ protected:
|
|||
*/
|
||||
PathsInChroot getPathsInSandbox();
|
||||
|
||||
virtual void setBuildTmpDir()
|
||||
{
|
||||
tmpDir = topTmpDir;
|
||||
tmpDirInSandbox = topTmpDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by prepareBuild() to do any setup in the parent to
|
||||
* prepare for a sandboxed build.
|
||||
*/
|
||||
virtual void prepareSandbox();
|
||||
|
||||
virtual Strings getPreBuildHookArgs()
|
||||
{
|
||||
return Strings({store.printStorePath(drvPath)});
|
||||
}
|
||||
|
||||
virtual Path realPathInSandbox(const Path & p)
|
||||
{
|
||||
return store.toRealPath(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the slave side of the pseudoterminal and use it as stderr.
|
||||
*/
|
||||
|
@ -370,9 +374,13 @@ public:
|
|||
|
||||
void killSandbox(bool getStats) override;
|
||||
|
||||
protected:
|
||||
|
||||
virtual void cleanupBuild();
|
||||
|
||||
private:
|
||||
|
||||
bool cleanupDecideWhetherDiskFull();
|
||||
bool decideWhetherDiskFull();
|
||||
|
||||
/**
|
||||
* Create alternative path calculated from but distinct from the
|
||||
|
@ -462,11 +470,10 @@ bool DerivationBuilderImpl::prepareBuild()
|
|||
{
|
||||
if (useBuildUsers()) {
|
||||
if (!buildUser)
|
||||
buildUser = acquireUserLock(drvOptions.useUidRange(drv) ? 65536 : 1, useChroot);
|
||||
buildUser = getBuildUser();
|
||||
|
||||
if (!buildUser) {
|
||||
if (!buildUser)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -528,7 +535,9 @@ std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> Derivation
|
|||
/* Check the exit status. */
|
||||
if (!statusOk(status)) {
|
||||
|
||||
diskFull |= cleanupDecideWhetherDiskFull();
|
||||
diskFull |= decideWhetherDiskFull();
|
||||
|
||||
cleanupBuild();
|
||||
|
||||
auto msg = fmt(
|
||||
"Cannot build '%s'.\n"
|
||||
|
@ -582,6 +591,10 @@ std::variant<std::pair<BuildResult::Status, Error>, SingleDrvOutputs> Derivation
|
|||
}
|
||||
}
|
||||
|
||||
void DerivationBuilderImpl::cleanupBuild()
|
||||
{
|
||||
deleteTmpDir(false);
|
||||
}
|
||||
|
||||
static void chmod_(const Path & path, mode_t mode)
|
||||
{
|
||||
|
@ -644,10 +657,7 @@ static void replaceValidPath(const Path & storePath, const Path & tmpPath)
|
|||
deletePath(oldPath);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool DerivationBuilderImpl::cleanupDecideWhetherDiskFull()
|
||||
bool DerivationBuilderImpl::decideWhetherDiskFull()
|
||||
{
|
||||
bool diskFull = false;
|
||||
|
||||
|
@ -670,19 +680,6 @@ bool DerivationBuilderImpl::cleanupDecideWhetherDiskFull()
|
|||
}
|
||||
#endif
|
||||
|
||||
deleteTmpDir(false);
|
||||
|
||||
/* Move paths out of the chroot for easier debugging of
|
||||
build failures. */
|
||||
if (useChroot && buildMode == bmNormal)
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.known) continue;
|
||||
if (buildMode != bmCheck && status.known->isValid()) continue;
|
||||
auto p = store.toRealPath(status.known->path);
|
||||
if (pathExists(chrootRootDir + p))
|
||||
std::filesystem::rename((chrootRootDir + p), p);
|
||||
}
|
||||
|
||||
return diskFull;
|
||||
}
|
||||
|
||||
|
@ -832,23 +829,9 @@ void DerivationBuilderImpl::startBuilder()
|
|||
/* Create a temporary directory where the build will take
|
||||
place. */
|
||||
topTmpDir = createTempDir(settings.buildDir.get().value_or(""), "nix-build-" + std::string(drvPath.name()), 0700);
|
||||
#ifdef __APPLE__
|
||||
if (false) {
|
||||
#else
|
||||
if (useChroot) {
|
||||
#endif
|
||||
/* If sandboxing is enabled, put the actual TMPDIR underneath
|
||||
an inaccessible root-owned directory, to prevent outside
|
||||
access.
|
||||
|
||||
On macOS, we don't use an actual chroot, so this isn't
|
||||
possible. Any mitigation along these lines would have to be
|
||||
done directly in the sandbox profile. */
|
||||
tmpDir = topTmpDir + "/build";
|
||||
createDir(tmpDir, 0700);
|
||||
} else {
|
||||
tmpDir = topTmpDir;
|
||||
}
|
||||
setBuildTmpDir();
|
||||
assert(!tmpDir.empty());
|
||||
assert(!tmpDirInSandbox.empty());
|
||||
chownToBuilder(tmpDir);
|
||||
|
||||
for (auto & [outputName, status] : initialOutputs) {
|
||||
|
@ -1053,14 +1036,12 @@ DerivationBuilderImpl::PathsInChroot DerivationBuilderImpl::getPathsInSandbox()
|
|||
|
||||
if (settings.preBuildHook != "") {
|
||||
printMsg(lvlChatty, "executing pre-build hook '%1%'", settings.preBuildHook);
|
||||
auto args = useChroot ? Strings({store.printStorePath(drvPath), chrootRootDir}) :
|
||||
Strings({ store.printStorePath(drvPath) });
|
||||
enum BuildHookState {
|
||||
stBegin,
|
||||
stExtraChrootDirs
|
||||
};
|
||||
auto state = stBegin;
|
||||
auto lines = runProgram(settings.preBuildHook, false, args);
|
||||
auto lines = runProgram(settings.preBuildHook, false, getPreBuildHookArgs());
|
||||
auto lastPos = std::string::size_type{0};
|
||||
for (auto nlPos = lines.find('\n'); nlPos != std::string::npos;
|
||||
nlPos = lines.find('\n', lastPos))
|
||||
|
@ -1157,14 +1138,6 @@ void DerivationBuilderImpl::processSandboxSetupMessages()
|
|||
|
||||
void DerivationBuilderImpl::initTmpDir()
|
||||
{
|
||||
/* In a sandbox, for determinism, always use the same temporary
|
||||
directory. */
|
||||
#ifdef __linux__
|
||||
tmpDirInSandbox = useChroot ? settings.sandboxBuildDir : tmpDir;
|
||||
#else
|
||||
tmpDirInSandbox = tmpDir;
|
||||
#endif
|
||||
|
||||
/* In non-structured mode, set all bindings either directory in the
|
||||
environment or via a file, as specified by
|
||||
`DerivationOptions::passAsFile`. */
|
||||
|
@ -1653,14 +1626,6 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
|
|||
for (auto & i : scratchOutputs) referenceablePaths.insert(i.second);
|
||||
for (auto & p : addedPaths) referenceablePaths.insert(p);
|
||||
|
||||
/* FIXME `needsHashRewrite` should probably be removed and we get to the
|
||||
real reason why we aren't using the chroot dir */
|
||||
auto toRealPathChroot = [&](const Path & p) -> Path {
|
||||
return useChroot && !needsHashRewrite()
|
||||
? chrootRootDir + p
|
||||
: store.toRealPath(p);
|
||||
};
|
||||
|
||||
/* Check whether the output paths were created, and make all
|
||||
output paths read-only. Then get the references of each output (that we
|
||||
might need to register), so we can topologically sort them. For the ones
|
||||
|
@ -1677,7 +1642,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
|
|||
throw BuildError(
|
||||
"builder for '%s' has no scratch output for '%s'",
|
||||
store.printStorePath(drvPath), outputName);
|
||||
auto actualPath = toRealPathChroot(store.printStorePath(*scratchOutput));
|
||||
auto actualPath = realPathInSandbox(store.printStorePath(*scratchOutput));
|
||||
|
||||
outputsToSort.insert(outputName);
|
||||
|
||||
|
@ -1786,7 +1751,7 @@ SingleDrvOutputs DerivationBuilderImpl::registerOutputs()
|
|||
auto output = get(drv.outputs, outputName);
|
||||
auto scratchPath = get(scratchOutputs, outputName);
|
||||
assert(output && scratchPath);
|
||||
auto actualPath = toRealPathChroot(store.printStorePath(*scratchPath));
|
||||
auto actualPath = realPathInSandbox(store.printStorePath(*scratchPath));
|
||||
|
||||
auto finish = [&](StorePath finalStorePath) {
|
||||
/* Store the final path */
|
||||
|
@ -2361,10 +2326,14 @@ StorePath DerivationBuilderImpl::makeFallbackPath(const StorePath & path)
|
|||
Hash(HashAlgorithm::SHA256), path.name());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// FIXME: do this properly
|
||||
#include "linux-derivation-builder.cc"
|
||||
#include "darwin-derivation-builder.cc"
|
||||
|
||||
namespace nix {
|
||||
|
||||
std::unique_ptr<DerivationBuilder> makeDerivationBuilder(
|
||||
Store & store,
|
||||
std::unique_ptr<DerivationBuilderCallbacks> miscMethods,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#ifdef __linux__
|
||||
|
||||
namespace nix {
|
||||
|
||||
struct LinuxDerivationBuilder : DerivationBuilderImpl
|
||||
{
|
||||
/**
|
||||
|
@ -20,23 +22,56 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
*/
|
||||
bool usingUserNamespace = true;
|
||||
|
||||
/**
|
||||
* The root of the chroot environment.
|
||||
*/
|
||||
Path chrootRootDir;
|
||||
|
||||
PathsInChroot pathsInChroot;
|
||||
|
||||
LinuxDerivationBuilder(
|
||||
Store & store, std::unique_ptr<DerivationBuilderCallbacks> miscMethods, DerivationBuilderParams params)
|
||||
: DerivationBuilderImpl(store, std::move(miscMethods), std::move(params))
|
||||
{
|
||||
useChroot = true;
|
||||
}
|
||||
|
||||
uid_t sandboxUid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); }
|
||||
gid_t sandboxGid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID(); }
|
||||
uid_t sandboxUid()
|
||||
{
|
||||
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID();
|
||||
}
|
||||
|
||||
gid_t sandboxGid()
|
||||
{
|
||||
return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID();
|
||||
}
|
||||
|
||||
bool needsHashRewrite() override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<UserLock> getBuildUser() override
|
||||
{
|
||||
return acquireUserLock(drvOptions.useUidRange(drv) ? 65536 : 1, true);
|
||||
}
|
||||
|
||||
void setBuildTmpDir() override
|
||||
{
|
||||
/* If sandboxing is enabled, put the actual TMPDIR underneath
|
||||
an inaccessible root-owned directory, to prevent outside
|
||||
access.
|
||||
|
||||
On macOS, we don't use an actual chroot, so this isn't
|
||||
possible. Any mitigation along these lines would have to be
|
||||
done directly in the sandbox profile. */
|
||||
tmpDir = topTmpDir + "/build";
|
||||
createDir(tmpDir, 0700);
|
||||
|
||||
/* In a sandbox, for determinism, always use the same temporary
|
||||
directory. */
|
||||
tmpDirInSandbox = settings.sandboxBuildDir;
|
||||
}
|
||||
|
||||
void prepareSandbox() override
|
||||
{
|
||||
/* Create a temporary directory in which we set up the chroot
|
||||
|
@ -59,7 +94,10 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
|
||||
throw SysError("cannot create '%1%'", chrootRootDir);
|
||||
|
||||
if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID()) == -1)
|
||||
if (buildUser
|
||||
&& chown(
|
||||
chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID())
|
||||
== -1)
|
||||
throw SysError("cannot change ownership of '%1%'", chrootRootDir);
|
||||
|
||||
/* Create a writable /tmp in the chroot. Many builders need
|
||||
|
@ -81,10 +119,12 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
|
||||
/* Declare the build user's group so that programs get a consistent
|
||||
view of the system (e.g., "id -gn"). */
|
||||
writeFile(chrootRootDir + "/etc/group",
|
||||
writeFile(
|
||||
chrootRootDir + "/etc/group",
|
||||
fmt("root:x:0:\n"
|
||||
"nixbld:!:%1%:\n"
|
||||
"nogroup:x:65534:\n", sandboxGid()));
|
||||
"nogroup:x:65534:\n",
|
||||
sandboxGid()));
|
||||
|
||||
/* Create /etc/hosts with localhost entry. */
|
||||
if (derivationType.isSandboxed())
|
||||
|
@ -125,7 +165,7 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
chownToBuilder(*cgroup);
|
||||
chownToBuilder(*cgroup + "/cgroup.procs");
|
||||
chownToBuilder(*cgroup + "/cgroup.threads");
|
||||
//chownToBuilder(*cgroup + "/cgroup.subtree_control");
|
||||
// chownToBuilder(*cgroup + "/cgroup.subtree_control");
|
||||
}
|
||||
|
||||
pathsInChroot = getPathsInSandbox();
|
||||
|
@ -136,6 +176,18 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
}
|
||||
}
|
||||
|
||||
Strings getPreBuildHookArgs() override
|
||||
{
|
||||
assert(!chrootRootDir.empty());
|
||||
return Strings({store.printStorePath(drvPath), chrootRootDir});
|
||||
}
|
||||
|
||||
Path realPathInSandbox(const Path & p) override
|
||||
{
|
||||
// FIXME: why the needsHashRewrite() conditional?
|
||||
return !needsHashRewrite() ? chrootRootDir + p : store.toRealPath(p);
|
||||
}
|
||||
|
||||
void startChild() override
|
||||
{
|
||||
/* Set up private namespaces for the build:
|
||||
|
@ -194,7 +246,8 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
if (errno != EPERM)
|
||||
throw SysError("setgroups failed");
|
||||
if (settings.requireDropSupplementaryGroups)
|
||||
throw Error("setgroups failed. Set the require-drop-supplementary-groups option to false to skip this step.");
|
||||
throw Error(
|
||||
"setgroups failed. Set the require-drop-supplementary-groups option to false to skip this step.");
|
||||
}
|
||||
|
||||
ProcessOptions options;
|
||||
|
@ -226,9 +279,7 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
|
||||
/* Close the write side to prevent runChild() from hanging
|
||||
reading from this. */
|
||||
Finally cleanup([&]() {
|
||||
userNamespaceSync.writeSide = -1;
|
||||
});
|
||||
Finally cleanup([&]() { userNamespaceSync.writeSide = -1; });
|
||||
|
||||
auto ss = tokenizeString<std::vector<std::string>>(readLine(sendPid.readSide.get()));
|
||||
assert(ss.size() == 1);
|
||||
|
@ -242,30 +293,32 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
uid_t hostGid = buildUser ? buildUser->getGID() : getgid();
|
||||
uid_t nrIds = buildUser ? buildUser->getUIDCount() : 1;
|
||||
|
||||
writeFile("/proc/" + std::to_string(pid) + "/uid_map",
|
||||
fmt("%d %d %d", sandboxUid(), hostUid, nrIds));
|
||||
writeFile("/proc/" + std::to_string(pid) + "/uid_map", fmt("%d %d %d", sandboxUid(), hostUid, nrIds));
|
||||
|
||||
if (!buildUser || buildUser->getUIDCount() == 1)
|
||||
writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny");
|
||||
|
||||
writeFile("/proc/" + std::to_string(pid) + "/gid_map",
|
||||
fmt("%d %d %d", sandboxGid(), hostGid, nrIds));
|
||||
writeFile("/proc/" + std::to_string(pid) + "/gid_map", fmt("%d %d %d", sandboxGid(), hostGid, nrIds));
|
||||
} else {
|
||||
debug("note: not using a user namespace");
|
||||
if (!buildUser)
|
||||
throw Error("cannot perform a sandboxed build because user namespaces are not enabled; check /proc/sys/user/max_user_namespaces");
|
||||
throw Error(
|
||||
"cannot perform a sandboxed build because user namespaces are not enabled; check /proc/sys/user/max_user_namespaces");
|
||||
}
|
||||
|
||||
/* Now that we now the sandbox uid, we can write
|
||||
/etc/passwd. */
|
||||
writeFile(chrootRootDir + "/etc/passwd", fmt(
|
||||
"root:x:0:0:Nix build user:%3%:/noshell\n"
|
||||
writeFile(
|
||||
chrootRootDir + "/etc/passwd",
|
||||
fmt("root:x:0:0:Nix build user:%3%:/noshell\n"
|
||||
"nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n"
|
||||
"nobody:x:65534:65534:Nobody:/:/noshell\n",
|
||||
sandboxUid(), sandboxGid(), settings.sandboxBuildDir));
|
||||
sandboxUid(),
|
||||
sandboxGid(),
|
||||
settings.sandboxBuildDir));
|
||||
|
||||
/* Save the mount- and user namespace of the child. We have to do this
|
||||
*before* the child does a chroot. */
|
||||
*before* the child does a chroot. */
|
||||
sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY);
|
||||
if (sandboxMountNamespace.get() == -1)
|
||||
throw SysError("getting sandbox mount namespace");
|
||||
|
@ -528,6 +581,24 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
return DerivationBuilderImpl::unprepareBuild();
|
||||
}
|
||||
|
||||
void cleanupBuild() override
|
||||
{
|
||||
DerivationBuilderImpl::cleanupBuild();
|
||||
|
||||
/* Move paths out of the chroot for easier debugging of
|
||||
build failures. */
|
||||
if (buildMode == bmNormal)
|
||||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.known)
|
||||
continue;
|
||||
if (buildMode != bmCheck && status.known->isValid())
|
||||
continue;
|
||||
auto p = store.toRealPath(status.known->path);
|
||||
if (pathExists(chrootRootDir + p))
|
||||
std::filesystem::rename((chrootRootDir + p), p);
|
||||
}
|
||||
}
|
||||
|
||||
void addDependency(const StorePath & path) override
|
||||
{
|
||||
if (isAllowed(path))
|
||||
|
@ -568,4 +639,6 @@ struct LinuxDerivationBuilder : DerivationBuilderImpl
|
|||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue