Skip to content
Closed
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
13 changes: 12 additions & 1 deletion src/libexpr/eval.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,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 @@ -287,7 +288,7 @@ EvalState::EvalState(
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
if (settings.pureEval || store->storeDir != realStoreDir) {
accessor = settings.pureEval
? storeFS
? storeFS.cast<SourceAccessor>()
: makeUnionSourceAccessor({accessor, storeFS});
}

Expand Down Expand Up @@ -3090,6 +3091,11 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_

auto res = (r / CanonPath(suffix)).resolveSymlinks();
if (res.pathExists()) return res;

// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
Comment on lines +3095 to +3096
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a hack?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah where is FilteringSourceAccessor even being used anymore? I only see it used with GitExportIgnoreSourceAccessor

if (auto accessor = res.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(res.path);
}

if (hasPrefix(path, "nix/"))
Expand Down Expand Up @@ -3160,6 +3166,11 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
if (path.resolveSymlinks().pathExists())
return finish(std::move(path));
else {
// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
Comment on lines +3169 to +3170
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a hack?

if (auto accessor = path.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(path.path);

logWarning({
.msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value)
});
Expand Down
3 changes: 2 additions & 1 deletion src/libexpr/include/nix/expr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class StorePath;
struct SingleDerivedPath;
enum RepairFlag : bool;
struct MemorySourceAccessor;
struct MountedSourceAccessor;
namespace eval_cache {
class EvalCache;
}
Expand Down Expand Up @@ -271,7 +272,7 @@ public:
/**
* The accessor corresponding to `store`.
*/
const ref<SourceAccessor> storeFS;
const ref<MountedSourceAccessor> storeFS;

/**
* The accessor for the root filesystem.
Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/primops/fetchMercurial.cc
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));

auto [storePath, input2] = input.fetchToStore(state.store);
auto [storePath, accessor, input2] = input.fetchToStore(state.store);

auto attrs2 = state.buildBindings(8);
state.mkStorePathString(storePath, attrs2.alloc(state.sOutPath));
Expand Down
5 changes: 4 additions & 1 deletion src/libexpr/primops/fetchTree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "nix/util/url.hh"
#include "nix/expr/value-to-json.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/util/mounted-source-accessor.hh"

#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -204,10 +205,12 @@ static void fetchTree(
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
}

auto [storePath, input2] = input.fetchToStore(state.store);
auto [storePath, accessor, input2] = input.fetchToStore(state.store);

state.allowPath(storePath);

state.storeFS->mount(CanonPath(state.store->printStorePath(storePath)), accessor);

emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
}

Expand Down
32 changes: 14 additions & 18 deletions src/libfetchers/fetchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -187,34 +187,30 @@ bool Input::contains(const Input & other) const
}

// FIXME: remove
std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
std::tuple<StorePath, ref<SourceAccessor>, Input> Input::fetchToStore(ref<Store> store) const
{
if (!scheme)
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));

auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
try {
auto [accessor, result] = getAccessorUnchecked(store);

auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, result.getName());
try {
auto [accessor, result] = getAccessorUnchecked(store);

auto narHash = store->queryPathInfo(storePath)->narHash;
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
auto storePath = nix::fetchToStore(*store, SourcePath(accessor), FetchMode::Copy, result.getName());

result.attrs.insert_or_assign("__final", Explicit<bool>(true));
auto narHash = store->queryPathInfo(storePath)->narHash;
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));

assert(result.isFinal());
result.attrs.insert_or_assign("__final", Explicit<bool>(true));

checkLocks(*this, result);
assert(result.isFinal());

return {storePath, result};
} catch (Error & e) {
e.addTrace({}, "while fetching the input '%s'", to_string());
throw;
}
}();
checkLocks(*this, result);

return {std::move(storePath), input};
return {std::move(storePath), accessor, result};
} catch (Error & e) {
e.addTrace({}, "while fetching the input '%s'", to_string());
throw;
}
}

void Input::checkLocks(Input specified, Input & result)
Expand Down
7 changes: 6 additions & 1 deletion src/libfetchers/filtering-source-accessor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,14 @@ bool FilteringSourceAccessor::pathExists(const CanonPath & path)
}

std::optional<SourceAccessor::Stat> FilteringSourceAccessor::maybeLstat(const CanonPath & path)
{
return isAllowed(path) ? next->maybeLstat(prefix / path) : std::nullopt;
}

SourceAccessor::Stat FilteringSourceAccessor::lstat(const CanonPath & path)
{
checkAccess(path);
return next->maybeLstat(prefix / path);
return next->lstat(prefix / path);
}

SourceAccessor::DirEntries FilteringSourceAccessor::readDirectory(const CanonPath & path)
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
2 changes: 1 addition & 1 deletion src/libfetchers/include/nix/fetchers/fetchers.hh
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public:
* Fetch the entire input into the Nix store, returning the
* location in the Nix store and the locked input.
*/
std::pair<StorePath, Input> fetchToStore(ref<Store> store) const;
std::tuple<StorePath, ref<SourceAccessor>, Input> fetchToStore(ref<Store> store) const;

/**
* Check the locking attributes in `result` against
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ struct FilteringSourceAccessor : SourceAccessor

bool pathExists(const CanonPath & path) override;

Stat lstat(const CanonPath & path) override;

std::optional<Stat> maybeLstat(const CanonPath & path) override;

DirEntries readDirectory(const CanonPath & path) override;
Expand Down
5 changes: 4 additions & 1 deletion src/libflake/flake/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "nix/store/local-fs-store.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/fetchers/input-cache.hh"

#include <nlohmann/json.hpp>
Expand All @@ -32,7 +33,9 @@ static StorePath copyInputToStore(
{
auto storePath = fetchToStore(*state.store, accessor, FetchMode::Copy, input.getName());

state.allowPath(storePath);
state.allowPath(storePath); // FIXME: should just whitelist the entire virtual store
Copy link
Member

Choose a reason for hiding this comment

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

Not a bug:

Suggested change
state.allowPath(storePath); // FIXME: should just whitelist the entire virtual store
state.allowPath(storePath); // TODO: we could simply whitelist the entire virtual store, because all of that is created by the evaluator and its allowed external sources


state.storeFS->mount(CanonPath(state.store->printStorePath(storePath)), accessor);

auto narHash = state.store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
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 @@ -43,6 +43,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
14 changes: 14 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,14 @@
#pragma once

#include "source-accessor.hh"

namespace nix {

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

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

}
4 changes: 1 addition & 3 deletions src/libutil/include/nix/util/source-accessor.hh
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
std::string typeString();
};

Stat lstat(const CanonPath & path);
virtual Stat lstat(const CanonPath & path);

virtual std::optional<Stat> maybeLstat(const CanonPath & path) = 0;

Expand Down 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
22 changes: 17 additions & 5 deletions src/libutil/mounted-source-accessor.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include "nix/util/source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"

namespace nix {

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

MountedSourceAccessor(std::map<CanonPath, ref<SourceAccessor>> _mounts)
MountedSourceAccessorImpl(std::map<CanonPath, ref<SourceAccessor>> _mounts)
: mounts(std::move(_mounts))
{
displayPrefix.clear();
Expand All @@ -23,6 +23,12 @@ struct MountedSourceAccessor : SourceAccessor
return accessor->readFile(subpath);
}

Stat lstat(const CanonPath & path) override
{
auto [accessor, subpath] = resolve(path);
return accessor->lstat(subpath);
}

std::optional<Stat> maybeLstat(const CanonPath & path) override
{
auto [accessor, subpath] = resolve(path);
Expand Down Expand Up @@ -69,11 +75,17 @@ struct MountedSourceAccessor : SourceAccessor
auto [accessor, subpath] = resolve(path);
return accessor->getPhysicalPath(subpath);
}

void mount(CanonPath mountPoint, ref<SourceAccessor> accessor) override
{
// FIXME: thread-safety
mounts.insert_or_assign(std::move(mountPoint), accessor);
}
};

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

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

using namespace nix;

Expand Down
2 changes: 1 addition & 1 deletion src/nix/flake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1095,7 +1095,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun
storePath =
dryRun
? (*inputNode)->lockedRef.input.computeStorePath(*store)
: (*inputNode)->lockedRef.input.fetchToStore(store).first;
: std::get<0>((*inputNode)->lockedRef.input.fetchToStore(store));
sources.insert(*storePath);
}
if (json) {
Expand Down
34 changes: 34 additions & 0 deletions tests/functional/flakes/source-paths.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ cat > "$repo/flake.nix" <<EOF
{
outputs = { ... }: {
x = 1;
y = assert false; 1;
z = builtins.readFile ./foo;
a = import ./foo;
b = import ./dir;
};
}
EOF
Expand All @@ -21,3 +25,33 @@ expectStderr 1 nix eval "$repo#x" | grepQuiet "error: Path 'flake.nix' in the re
git -C "$repo" add flake.nix

[[ $(nix eval "$repo#x") = 1 ]]

expectStderr 1 nix eval "$repo#y" | grepQuiet "at $repo/flake.nix:"

git -C "$repo" commit -a -m foo

expectStderr 1 nix eval "git+file://$repo?ref=master#y" | grepQuiet "at «git+file://$repo?ref=master&rev=.*»/flake.nix:"

expectStderr 1 nix eval "$repo#z" | grepQuiet "error: Path 'foo' does not exist in Git repository \"$repo\"."
expectStderr 1 nix eval "git+file://$repo?ref=master#z" | grepQuiet "error: '«git+file://$repo?ref=master&rev=.*»/foo' does not exist"
expectStderr 1 nix eval "$repo#a" | grepQuiet "error: Path 'foo' does not exist in Git repository \"$repo\"."

echo 123 > "$repo/foo"

expectStderr 1 nix eval "$repo#z" | grepQuiet "error: Path 'foo' in the repository \"$repo\" is not tracked by Git."
expectStderr 1 nix eval "$repo#a" | grepQuiet "error: Path 'foo' in the repository \"$repo\" is not tracked by Git."

git -C "$repo" add "$repo/foo"

[[ $(nix eval --raw "$repo#z") = 123 ]]

expectStderr 1 nix eval "$repo#b" | grepQuiet "error: Path 'dir' does not exist in Git repository \"$repo\"."

mkdir -p "$repo/dir"
echo 456 > "$repo/dir/default.nix"

expectStderr 1 nix eval "$repo#b" | grepQuiet "error: Path 'dir' in the repository \"$repo\" is not tracked by Git."

git -C "$repo" add "$repo/dir/default.nix"

[[ $(nix eval "$repo#b") = 456 ]]
Loading