Skip to content
Draft
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
117 changes: 75 additions & 42 deletions src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,51 +242,78 @@ EvalState::EvalState(
, settings{settings}
, symbols(StaticEvalSymbols::staticSymbolTable())
, repair(NoRepair)
, storeFS(makeMountedSourceAccessor({
{CanonPath::root, makeEmptySourceAccessor()},
/* In the pure eval case, we can simply require
valid paths. However, in the *impure* eval
case this gets in the way of the union
mechanism, because an invalid access in the
upper layer will *not* be caught by the union
source accessor, but instead abort the entire
lookup.

This happens when the store dir in the
ambient file system has a path (e.g. because
another Nix store there), but the relocated
store does not.

TODO make the various source accessors doing
access control all throw the same type of
exception, and make union source accessor
catch it, so we don't need to do this hack.
*/
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
}))
, rootFS([&] {
/* In pure eval mode, we provide a filesystem that only
contains the Nix store.
, storeFS([&] {
auto accessor = makeMountedSourceAccessor({{CanonPath::root, makeEmptySourceAccessor()}});

/* In the pure eval case, we can simply require
valid paths. However, in the *impure* eval
case this gets in the way of the union
mechanism, because an invalid access in the
upper layer will *not* be caught by the union
source accessor, but instead abort the entire
lookup.

This happens when the store dir in the
ambient file system has a path (e.g. because
another Nix store there), but the relocated
store does not.

TODO make the various source accessors doing
access control all throw the same type of
exception, and make union source accessor
catch it, so we don't need to do this hack.
*/
if (settings.pureEval) {
/* This is just an overkill way to make sure other store
paths get this error, and not the "doesn't exist" error
that the mounted source accessor would do on its own. */
accessor->mount(
CanonPath::root,
AllowListSourceAccessor::create(
getFSSourceAccessor(),
{},
{CanonPath::root, CanonPath(store->storeDir)},
[&](const CanonPath & path) -> RestrictedPathError {
throw RestrictedPathError(
"access to absolute path '%1%' is forbidden in pure evaluation mode (use '--impure' to override)",
CanonPath(store->storeDir) / path);
}));
/* We don't want to list store paths */
accessor->mount(CanonPath(store->storeDir), makeEmptySourceAccessor());
Comment on lines +267 to +282
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is me trying to arrange the error reporting. It is not yet working. Another approach is fine.

} else {
accessor->mount(CanonPath(store->storeDir), store->getFSAccessor(false));
}

Otherwise, use a union accessor to make the augmented 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, and also for lazy
mounted fetchTree. */
auto accessor = settings.pureEval ? storeFS.cast<SourceAccessor>()
: makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});
return accessor;
}())
, rootFS([&] -> decltype(rootFS) {
/* In pure eval mode, we provide a filesystem that only
contains the Nix store. */
if (settings.pureEval)
return storeFS;

auto makeImpureAccessor = [&]() -> decltype(rootFS) {
/* 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)
accessor = AllowListSourceAccessor::create(
accessor, {}, {}, [&settings](const CanonPath & path) -> RestrictedPathError {
auto modeInformation = settings.pureEval ? "in pure evaluation mode (use '--impure' to override)"
: "in restricted mode";
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
if (settings.restrictEval)
return AllowListSourceAccessor::create(
makeImpureAccessor(), {}, {}, [](const CanonPath & path) -> RestrictedPathError {
throw RestrictedPathError("access to absolute path '%1%' is forbidden in restricted mode", path);
});
Comment on lines +310 to 314
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is simpler than before, because it is just for restricted eval now.


return accessor;
return makeImpureAccessor();
}())
, corepkgsFS(make_ref<MemorySourceAccessor>())
, internalFS(make_ref<MemorySourceAccessor>())
Expand Down Expand Up @@ -373,13 +400,19 @@ void EvalState::allowPathLegacy(const Path & path)

void EvalState::allowPath(const StorePath & storePath)
{
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
if (settings.pureEval) {
storeFS->mount(CanonPath(store->printStorePath(storePath)), ref{store->getFSAccessor(storePath)});
}
if (settings.restrictEval) {
auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>();
assert(rootFS2);
rootFS2->allowPrefix(CanonPath(store->printStorePath(storePath)));
}
}

void EvalState::allowClosure(const StorePath & storePath)
{
if (!rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
if (!settings.pureEval && !settings.restrictEval)
return;

StorePathSet closure;
Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/paths.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ SourcePath EvalState::rootPath(PathView path)

SourcePath EvalState::storePath(const StorePath & path)
{
return {rootFS, CanonPath{store->printStorePath(path)}};
return {storeFS, CanonPath{path.to_str()}};
}

StorePath
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/restricted.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ ln -sfn "${_NIX_TEST_SOURCE_DIR}/restricted-secret" "${_NIX_TEST_SOURCE_DIR}/res
mkdir -p "$traverseDir"
# shellcheck disable=SC2001
goUp="..$(echo "$traverseDir" | sed -e 's,[^/]\+,..,g')"
output="$(nix eval --raw --restrict-eval -I "$traverseDir" \
output="$(nix eval --raw --impure --restrict-eval -I "$traverseDir" \
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This was just a mistake I think --- it didn't mean to test pure and restricted evaluation together.

--expr "builtins.readFile \"$traverseDir/$goUp${_NIX_TEST_SOURCE_DIR}/restricted-innocent\"" \
2>&1 || :)"
echo "$output" | grep "is forbidden"
Expand Down
Loading