Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/libstore/include/nix/store/build/derivation-builder.hh
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ struct DerivationBuilderCallbacks
*/
struct DerivationBuilder : RestrictionContext
{
DerivationBuilder() = default;
explicit DerivationBuilder(const StorePathSet & inputPaths)
: RestrictionContext(inputPaths)
{
}

virtual ~DerivationBuilder() = default;

/**
Expand Down Expand Up @@ -181,6 +185,13 @@ struct DerivationBuilder : RestrictionContext
* killed.
*/
virtual bool killChild() = 0;

/**
* Called by `DerivationBuilderDeleter` before deletion. Runs
* cleanup logic that needs to call virtual methods (which
* cannot safely be called from a destructor).
*/
virtual void cleanupOnDestruction() noexcept {}
};

/**
Expand Down
46 changes: 35 additions & 11 deletions src/libstore/include/nix/store/restricted-store.hh
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@ struct LocalStoreConfig;
*/
struct RestrictionContext
{
/**
* Paths that are already allowed to begin with
*/
virtual const StorePathSet & originalPaths() = 0;

/**
* Paths that were added via recursive Nix calls.
*/
Expand All @@ -38,14 +33,35 @@ struct RestrictionContext
*/
std::set<DrvOutput> addedDrvOutputs;

explicit RestrictionContext(const StorePathSet & inputPaths)
: inputPaths_(inputPaths)
{
}

/**
* Paths that are already allowed to begin with.
*/
const StorePathSet & originalPaths()
{
return inputPaths_;
}

/**
* Recursive Nix calls are only allowed to build or realize paths
* in the original input closure or added via a recursive Nix call
* (so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
* /nix/store/<bla> is some arbitrary path in a binary cache).
*/
virtual bool isAllowed(const StorePath &) = 0;
virtual bool isAllowed(const DrvOutput & id) = 0;
bool isAllowed(const StorePath & path)
{
return inputPaths_.count(path) || addedPaths.count(path);
}

bool isAllowed(const DrvOutput & id)
{
return addedDrvOutputs.count(id);
}

bool isAllowed(const DerivedPath & id);

/**
Expand All @@ -62,13 +78,21 @@ struct RestrictionContext
virtual ~RestrictionContext() = default;

protected:
/**
* Called for each newly added dependency. The default
* implementation just records the path. Override to perform
* additional work (e.g. bind-mounting into a chroot).
*/
virtual void addDependencyImpl(const StorePath & path)
{
addedPaths.insert(path);
}

private:
/**
* This is the underlying implementation to be defined. The caller
* will ensure that this is only called on newly added dependencies,
* and that idempotent calls are a no-op.
* Reference to the original input paths, owned by the caller.
*/
virtual void addDependencyImpl(const StorePath & path) = 0;
const StorePathSet & inputPaths_;
};

/**
Expand Down
209 changes: 0 additions & 209 deletions src/libstore/unix/build/chroot-derivation-builder.cc
Original file line number Diff line number Diff line change
@@ -1,209 +0,0 @@
#if defined(__linux__) || defined(__FreeBSD__)

namespace nix {

struct ChrootDerivationBuilder : virtual DerivationBuilderImpl
{
ChrootDerivationBuilder(
LocalStore & store, std::unique_ptr<DerivationBuilderCallbacks> miscMethods, DerivationBuilderParams params)
: DerivationBuilderImpl{store, std::move(miscMethods), std::move(params)}
{
}

/**
* The root of the chroot environment.
*/
std::filesystem::path chrootRootDir;

/**
* RAII object to delete the chroot directory.
*/
std::shared_ptr<AutoDelete> autoDelChroot;

PathsInChroot pathsInChroot;

bool needsHashRewrite() override
{
return false;
}

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);
}

std::filesystem::path tmpDirInSandbox() override
{
/* In a sandbox, for determinism, always use the same temporary
directory. */
return store.config->getLocalSettings().sandboxBuildDir.get();
}

virtual gid_t sandboxGid()
{
return buildUser->getGID();
}

void prepareSandbox() override
{
/* Create a temporary directory in which we set up the chroot
environment using bind-mounts. We put it in the Nix store
so that the build outputs can be moved efficiently from the
chroot to their final location. */
auto chrootParentDir = store.toRealPath(drvPath);
chrootParentDir += ".chroot";
deletePath(chrootParentDir);

/* Clean up the chroot directory automatically. */
autoDelChroot = std::make_shared<AutoDelete>(chrootParentDir);

printMsg(lvlChatty, "setting up chroot environment in %1%", PathFmt(chrootParentDir));

if (mkdir(chrootParentDir.c_str(), 0700) == -1)
throw SysError("cannot create %s", PathFmt(chrootRootDir));

chrootRootDir = chrootParentDir / "root";

if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1)
throw SysError("cannot create %1%", PathFmt(chrootRootDir));

if (buildUser
&& chown(
chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID())
== -1)
throw SysError("cannot change ownership of %1%", PathFmt(chrootRootDir));

/* Create a writable /tmp in the chroot. Many builders need
this. (Of course they should really respect $TMPDIR
instead.) */
std::filesystem::path chrootTmpDir = chrootRootDir / "tmp";
createDirs(chrootTmpDir);
chmod(chrootTmpDir, 01777);

/* Create a /etc/passwd with entries for the build user and the
nobody account. The latter is kind of a hack to support
Samba-in-QEMU. */
createDirs(chrootRootDir / "etc");
if (drvOptions.useUidRange(drv))
chownToBuilder(chrootRootDir / "etc");

if (drvOptions.useUidRange(drv) && (!buildUser || buildUser->getUIDCount() < 65536))
throw Error(
"feature 'uid-range' requires the setting '%s' to be enabled",
store.config->getLocalSettings().autoAllocateUids.name);

/* Declare the build user's group so that programs get a consistent
view of the system (e.g., "id -gn"). */
writeFile(
chrootRootDir / "etc" / "group",
fmt("root:x:0:\n"
"nixbld:!:%1%:\n"
"nogroup:x:65534:\n",
sandboxGid()));

/* Create /etc/hosts with localhost entry. */
if (derivationType.isSandboxed())
writeFile(chrootRootDir / "etc" / "hosts", "127.0.0.1 localhost\n::1 localhost\n");

/* 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,
while other inputs are hard-linked (since only directories
can be bind-mounted). !!! As an extra security
precaution, make the fake Nix store only writable by the
build user. */
std::filesystem::path chrootStoreDir = chrootRootDir / std::filesystem::path(store.storeDir).relative_path();
createDirs(chrootStoreDir);
chmod(chrootStoreDir, 01775);

if (buildUser && chown(chrootStoreDir.c_str(), 0, buildUser->getGID()) == -1)
throw SysError("cannot change ownership of %1%", PathFmt(chrootStoreDir));

pathsInChroot = getPathsInSandbox();

for (auto & i : inputPaths) {
auto p = store.printStorePath(i);
pathsInChroot.insert_or_assign(p, ChrootPath{.source = store.toRealPath(i)});
}

/* If we're repairing, checking or rebuilding part of a
multiple-outputs derivation, it's possible that we're
rebuilding a path that is in settings.sandbox-paths
(typically the dependencies of /bin/sh). Throw them
out. */
for (auto & i : drv.outputsAndOptPaths(store)) {
/* If the name isn't known a priori (i.e. floating
content-addressing derivation), the temporary location we use
should be fresh. Freshness means it is impossible that the path
is already in the sandbox, so we don't need to worry about
removing it. */
if (i.second.second)
pathsInChroot.erase(store.printStorePath(*i.second.second));
}
}

Strings getPreBuildHookArgs() override
{
assert(!chrootRootDir.empty());
return Strings({store.printStorePath(drvPath), chrootRootDir.native()});
}

std::filesystem::path realPathInHost(const std::filesystem::path & p) override
{
// FIXME: why the needsHashRewrite() conditional?
return !needsHashRewrite() ? chrootRootDir / p.relative_path()
: std::filesystem::path(store.toRealPath(store.parseStorePath(p.native())));
}

void cleanupBuild(bool force) override
{
DerivationBuilderImpl::cleanupBuild(force);

/* Move paths out of the chroot for easier debugging of
build failures. */
if (!force && buildMode == bmNormal)
for (auto & [_, status] : initialOutputs) {
if (!status.known)
continue;
if (buildMode != bmCheck && status.known->isValid())
continue;
std::filesystem::path p = store.toRealPath(status.known->path);
std::filesystem::path chrootPath = chrootRootDir / p.relative_path();
if (pathExists(chrootPath))
std::filesystem::rename(chrootPath, p);
}

autoDelChroot.reset(); /* this runs the destructor */
}

std::pair<std::filesystem::path, std::filesystem::path> addDependencyPrep(const StorePath & path)
{
DerivationBuilderImpl::addDependencyImpl(path);

debug("materialising '%s' in the sandbox", store.printStorePath(path));

std::filesystem::path source = store.toRealPath(path);
std::filesystem::path target =
chrootRootDir / std::filesystem::path(store.printStorePath(path)).relative_path();

if (pathExists(target)) {
// There is a similar debug message in doBind, so only run it in this block to not have double messages.
debug("bind-mounting %s -> %s", PathFmt(target), PathFmt(source));
throw Error("store path '%s' already exists in the sandbox", store.printStorePath(path));
}

return {source, target};
}
};

} // namespace nix

#endif
Loading
Loading