diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 624d7d4aa..36f2cd7d7 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -273,7 +273,7 @@ EvalState::EvalState( /* Apply access control if needed. */ if (settings.restrictEval || settings.pureEval) - accessor = AllowListSourceAccessor::create(accessor, {}, + accessor = AllowListSourceAccessor::create(accessor, {}, {}, [&settings](const CanonPath & path) -> RestrictedPathError { auto modeInformation = settings.pureEval ? "in pure evaluation mode (use '--impure' to override)" diff --git a/src/libfetchers/filtering-source-accessor.cc b/src/libfetchers/filtering-source-accessor.cc index b1ba84140..72a3fb4eb 100644 --- a/src/libfetchers/filtering-source-accessor.cc +++ b/src/libfetchers/filtering-source-accessor.cc @@ -58,18 +58,23 @@ void FilteringSourceAccessor::checkAccess(const CanonPath & path) struct AllowListSourceAccessorImpl : AllowListSourceAccessor { std::set<CanonPath> allowedPrefixes; + std::unordered_set<CanonPath> allowedPaths; AllowListSourceAccessorImpl( ref<SourceAccessor> next, std::set<CanonPath> && allowedPrefixes, + std::unordered_set<CanonPath> && allowedPaths, MakeNotAllowedError && makeNotAllowedError) : AllowListSourceAccessor(SourcePath(next), std::move(makeNotAllowedError)) , allowedPrefixes(std::move(allowedPrefixes)) + , allowedPaths(std::move(allowedPaths)) { } bool isAllowed(const CanonPath & path) override { - return path.isAllowed(allowedPrefixes); + return + allowedPaths.contains(path) + || path.isAllowed(allowedPrefixes); } void allowPrefix(CanonPath prefix) override @@ -81,9 +86,14 @@ struct AllowListSourceAccessorImpl : AllowListSourceAccessor ref<AllowListSourceAccessor> AllowListSourceAccessor::create( ref<SourceAccessor> next, std::set<CanonPath> && allowedPrefixes, + std::unordered_set<CanonPath> && allowedPaths, MakeNotAllowedError && makeNotAllowedError) { - return make_ref<AllowListSourceAccessorImpl>(next, std::move(allowedPrefixes), std::move(makeNotAllowedError)); + return make_ref<AllowListSourceAccessorImpl>( + next, + std::move(allowedPrefixes), + std::move(allowedPaths), + std::move(makeNotAllowedError)); } bool CachingFilteringSourceAccessor::isAllowed(const CanonPath & path) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 2bef348de..58ba39889 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -1215,16 +1215,12 @@ ref<SourceAccessor> GitRepoImpl::getAccessor( ref<SourceAccessor> GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) { auto self = ref<GitRepoImpl>(shared_from_this()); - /* In case of an empty workdir, return an empty in-memory tree. We - cannot use AllowListSourceAccessor because it would return an - error for the root (and we can't add the root to the allow-list - since that would allow access to all its children). */ ref<SourceAccessor> fileAccessor = - wd.files.empty() - ? makeEmptySourceAccessor() - : AllowListSourceAccessor::create( + AllowListSourceAccessor::create( makeFSSourceAccessor(path), - std::set<CanonPath> { wd.files }, + std::set<CanonPath>{ wd.files }, + // Always allow access to the root, but not its children. + std::unordered_set<CanonPath>{CanonPath::root}, std::move(makeNotAllowedError)).cast<SourceAccessor>(); if (exportIgnore) return make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt); diff --git a/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh b/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh index 0e6b71e9a..2b59f03ca 100644 --- a/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh +++ b/src/libfetchers/include/nix/fetchers/filtering-source-accessor.hh @@ -2,6 +2,8 @@ #include "nix/util/source-path.hh" +#include <unordered_set> + namespace nix { /** @@ -70,6 +72,7 @@ struct AllowListSourceAccessor : public FilteringSourceAccessor static ref<AllowListSourceAccessor> create( ref<SourceAccessor> next, std::set<CanonPath> && allowedPrefixes, + std::unordered_set<CanonPath> && allowedPaths, MakeNotAllowedError && makeNotAllowedError); using FilteringSourceAccessor::FilteringSourceAccessor; diff --git a/tests/functional/flakes/meson.build b/tests/functional/flakes/meson.build index 74ff3d91d..b8c650db4 100644 --- a/tests/functional/flakes/meson.build +++ b/tests/functional/flakes/meson.build @@ -29,7 +29,8 @@ suites += { 'non-flake-inputs.sh', 'relative-paths.sh', 'symlink-paths.sh', - 'debugger.sh' + 'debugger.sh', + 'source-paths.sh', ], 'workdir': meson.current_source_dir(), } diff --git a/tests/functional/flakes/source-paths.sh b/tests/functional/flakes/source-paths.sh new file mode 100644 index 000000000..4709bf2fc --- /dev/null +++ b/tests/functional/flakes/source-paths.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +source ./common.sh + +requireGit + +repo=$TEST_ROOT/repo + +createGitRepo "$repo" + +cat > "$repo/flake.nix" <<EOF +{ + outputs = { ... }: { + x = 1; + }; +} +EOF + +expectStderr 1 nix eval "$repo#x" | grepQuiet "error: Path 'flake.nix' in the repository \"$repo\" is not tracked by Git." + +git -C "$repo" add flake.nix + +[[ $(nix eval "$repo#x") = 1 ]]