Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 24 additions & 20 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "nix/expr/print.hh"
#include "nix/fetchers/filtering-source-accessor.hh"
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/expr/gc-small-vector.hh"
#include "nix/util/url.hh"
#include "nix/fetchers/fetch-to-store.hh"
Expand Down Expand Up @@ -225,22 +226,25 @@ EvalState::EvalState(
*/
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
}))
, rootFS(({
/* In pure eval mode, we provide a filesystem that only
contains the Nix store.

If we have a chroot store and pure eval is not enabled,
use a union accessor to make the chroot store available
at its logical location while still having the
underlying directory available. This is necessary for
instance if we're evaluating a file from the physical
/nix/store while using a chroot store. */
auto accessor = getFSSourceAccessor();

auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
if (settings.pureEval || store->storeDir != realStoreDir) {
accessor = settings.pureEval ? storeFS : makeUnionSourceAccessor({accessor, storeFS});
}
, rootFS([&] {
auto accessor = [&]() -> decltype(rootFS) {
/* In pure eval mode, we provide a filesystem that only
contains the Nix store. */
if (settings.pureEval)
return storeFS;

/* If we have a chroot store and pure eval is not enabled,
use a union accessor to make the chroot store available
at its logical location while still having the underlying
directory available. This is necessary for instance if
we're evaluating a file from the physical /nix/store
while using a chroot store. */
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
if (store->storeDir != realStoreDir)
return makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});

return getFSSourceAccessor();
}();

/* Apply access control if needed. */
if (settings.restrictEval || settings.pureEval)
Expand All @@ -251,8 +255,8 @@ EvalState::EvalState(
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
});

accessor;
}))
return accessor;
}())
, corepkgsFS(make_ref<MemorySourceAccessor>())
, internalFS(make_ref<MemorySourceAccessor>())
, derivationInternal{corepkgsFS->addFile(
Expand Down Expand Up @@ -333,7 +337,7 @@ EvalState::EvalState(

EvalState::~EvalState() {}

void EvalState::allowPath(const Path & path)
void EvalState::allowPathLegacy(const Path & path)
{
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
rootFS2->allowPrefix(CanonPath(path));
Expand Down Expand Up @@ -3176,7 +3180,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat

/* Allow access to paths in the search path. */
if (initAccessControl) {
allowPath(path.path.abs());
allowPathLegacy(path.path.abs());
if (store->isInStore(path.path.abs())) {
try {
allowClosure(store->toStorePath(path.path.abs()).first);
Expand Down
8 changes: 6 additions & 2 deletions src/libexpr/include/nix/expr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class StorePath;
struct SingleDerivedPath;
enum RepairFlag : bool;
struct MemorySourceAccessor;
struct MountedSourceAccessor;

namespace eval_cache {
class EvalCache;
Expand Down Expand Up @@ -319,7 +320,7 @@ public:
/**
* The accessor corresponding to `store`.
*/
const ref<SourceAccessor> storeFS;
const ref<MountedSourceAccessor> storeFS;

/**
* The accessor for the root filesystem.
Expand Down Expand Up @@ -488,8 +489,11 @@ public:

/**
* Allow access to a path.
*
* Only for restrict eval: pure eval just whitelist store paths,
* never arbitrary paths.
Comment on lines +493 to +494
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation doesn't yet enforce this, but I think this does track for the one place this is in fact used now.

*/
void allowPath(const Path & path);
void allowPathLegacy(const Path & path);

/**
* Allow access to a store path. Note that this gets remapped to
Expand Down
1 change: 1 addition & 0 deletions src/libfetchers/git.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "nix/fetchers/fetch-settings.hh"
#include "nix/util/json-utils.hh"
#include "nix/util/archive.hh"
#include "nix/util/mounted-source-accessor.hh"

#include <regex>
#include <string.h>
Expand Down
1 change: 1 addition & 0 deletions src/libutil/include/nix/util/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ headers = files(
'logging.hh',
'lru-cache.hh',
'memory-source-accessor.hh',
'mounted-source-accessor.hh',
'muxable-pipe.hh',
'os-string.hh',
'pool.hh',
Expand Down
20 changes: 20 additions & 0 deletions src/libutil/include/nix/util/mounted-source-accessor.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include "source-accessor.hh"

namespace nix {

struct MountedSourceAccessor : SourceAccessor
{
virtual void mount(CanonPath mountPoint, ref<SourceAccessor> accessor) = 0;

/**
* Return the accessor mounted on `mountPoint`, or `nullptr` if
* there is no such mount point.
*/
virtual std::shared_ptr<SourceAccessor> getMount(CanonPath mountPoint) = 0;
};

ref<MountedSourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts);

} // namespace nix
2 changes: 0 additions & 2 deletions src/libutil/include/nix/util/source-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,6 @@ ref<SourceAccessor> getFSSourceAccessor();
*/
ref<SourceAccessor> makeFSSourceAccessor(std::filesystem::path root);

ref<SourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts);

/**
* Construct an accessor that presents a "union" view of a vector of
* underlying accessors. Earlier accessors take precedence over later.
Expand Down
38 changes: 27 additions & 11 deletions src/libutil/mounted-source-accessor.cc
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
#include "nix/util/source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"

#include <boost/unordered/concurrent_flat_map.hpp>

namespace nix {

struct MountedSourceAccessor : SourceAccessor
struct MountedSourceAccessorImpl : MountedSourceAccessor
{
std::map<CanonPath, ref<SourceAccessor>> mounts;
boost::concurrent_flat_map<CanonPath, ref<SourceAccessor>> mounts;

MountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> _mounts)
: mounts(std::move(_mounts))
MountedSourceAccessorImpl(std::map<CanonPath, ref<SourceAccessor>> _mounts)
{
displayPrefix.clear();

// Currently we require a root filesystem. This could be relaxed.
assert(mounts.contains(CanonPath::root));
assert(_mounts.contains(CanonPath::root));

for (auto & [path, accessor] : _mounts)
mount(path, accessor);

// FIXME: return dummy parent directories automatically?
}
Expand Down Expand Up @@ -52,10 +56,9 @@ struct MountedSourceAccessor : SourceAccessor
// Find the nearest parent of `path` that is a mount point.
std::vector<std::string> subpath;
while (true) {
auto i = mounts.find(path);
if (i != mounts.end()) {
if (auto mount = getMount(path)) {
std::reverse(subpath.begin(), subpath.end());
return {i->second, CanonPath(subpath)};
return {ref(mount), CanonPath(subpath)};
}

assert(!path.isRoot());
Expand All @@ -69,11 +72,24 @@ struct MountedSourceAccessor : SourceAccessor
auto [accessor, subpath] = resolve(path);
return accessor->getPhysicalPath(subpath);
}

void mount(CanonPath mountPoint, ref<SourceAccessor> accessor) override
{
mounts.emplace(std::move(mountPoint), std::move(accessor));
}

std::shared_ptr<SourceAccessor> getMount(CanonPath mountPoint) override
{
if (auto res = getConcurrent(mounts, mountPoint))
return *res;
else
return nullptr;
}
};

ref<SourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts)
ref<MountedSourceAccessor> makeMountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> mounts)
{
return make_ref<MountedSourceAccessor>(std::move(mounts));
return make_ref<MountedSourceAccessorImpl>(std::move(mounts));
}

} // namespace nix
1 change: 1 addition & 0 deletions src/nix/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "nix/util/strings.hh"
#include "nix/util/executable-path.hh"
#include "nix/util/environment-variables.hh"
#include "nix/util/mounted-source-accessor.hh"

using namespace nix;

Expand Down
4 changes: 2 additions & 2 deletions src/nix/profile.cc
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ struct ProfileManifest

else if (std::filesystem::exists(profile / "manifest.nix")) {
// FIXME: needed because of pure mode; ugly.
state.allowPath(state.store->followLinksToStore(profile.string()));
state.allowPath(state.store->followLinksToStore((profile / "manifest.nix").string()));
state.allowPath(state.store->followLinksToStorePath(profile.string()));
state.allowPath(state.store->followLinksToStorePath((profile / "manifest.nix").string()));
Comment on lines +180 to +181
Copy link
Member Author

@Ericson2314 Ericson2314 Sep 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to whitelist just part of a store object, that feels weird. Now this does whole store objects.


auto packageInfos = queryInstalled(state, state.store->followLinksToStore(profile.string()));

Expand Down
Loading