Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix builtins.storePath in pure mode #8090

Closed
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
25 changes: 25 additions & 0 deletions src/libexpr/eval.hh
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,31 @@ struct EvalSettings : Config
builds to take place.
)"};

// TODO: (RFC 92) Add dynamic derivations as a use case here. Review the final assertion about non-buildable paths.
Setting<bool> enableStorePathReferences{
this, true, "allow-store-path-references",
R"(
Whether to allow references to store paths in the Nix language.

Usually, store paths are created by adding files to the store and
by using the `derivation` primitive. However, the `storePath`
primitive allows you to merely assert that a store path exists or
can be substituted, and allows you to use it in subsequent expressions.

This lets you use pre-built derivations, such as pre-built packages
that are passed into a NixOS VM test or closed source software that
is built and distributed with Nix.

However, unlike most expressions, it does not inherently provide a
means of obtaining, modifying and rebuilding the store path.
Unless these needs are covered by some process outside the current
evaluation context, use of this primitive suggests a lack of
reproducibility. You may disable this option to enforce that all
store paths are created by the current Nix evaluator. This ensures
that any non-reproducible content in your closure comes from
individual local files, built-in fetchers, or fixed-output derivations.
)"};

Setting<Strings> allowedUris{this, {}, "allowed-uris",
R"(
A list of URI prefixes to which access is allowed in restricted
Expand Down
48 changes: 32 additions & 16 deletions src/libexpr/primops.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1445,23 +1445,39 @@ static RegisterPrimOp primop_toPath({
corner cases. */
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (evalSettings.pureEval)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
.errPos = state.positions[pos]
}));

PathSet context;
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath"));
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true);
if (!state.store->isInStore(path))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos]
}));
Path path = state.coerceToPath(pos, *args[0], context, "while evaluating the argument of builtins.storePath");

/* The following branch permits a dependency on a store path to be declared,
but in restrict-eval, extending the allowed paths would not be desirable
regardless. */
if (evalSettings.pureEval && evalSettings.enableStorePathReferences && !evalSettings.restrictEval) {

/* Reading from the store behaves like a pure function, but we want to
be careful about symlink resolution, which would otherwise be done
shortly after. A mutable symlink maybe be present in the chain of
symlinks, so to keep it simple, we currently disallow store paths in
symlinks altogether when in pure mode. */
if (!state.store->isStorePath(path)) {
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'%s' is only allowed on store paths in pure evaluation mode", "builtins.storePath"),
.errPos = state.positions[pos]
}));
}
state.allowPath(path);
} else {
path = state.checkSourcePath(path);
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
directly in the store. The latter condition is necessary so
e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true);
if (!state.store->isInStore(path))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos]
}));
}

auto path2 = state.store->toStorePath(path).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
Expand Down
6 changes: 3 additions & 3 deletions src/libexpr/primops/fetchClosure.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
toPath = fromPath;
}

/* In pure mode, require a CA path. */
if (evalSettings.pureEval) {
/* In pure mode without enable-store-path-references, require a CA path. */
if (evalSettings.pureEval && !evalSettings.enableStorePathReferences) {
auto info = state.store->queryPathInfo(*toPath);
if (!info->isContentAddressed(*state.store))
throw Error({
.msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
.msg = hintfmt("in pure mode, with option 'enable-store-path-references' disabled, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
state.store->printStorePath(*toPath)),
.errPos = state.positions[pos]
});
Expand Down
2 changes: 1 addition & 1 deletion src/libexpr/tests/error_traces.cc
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ namespace nix {
ASSERT_TRACE2("storePath true",
TypeError,
hintfmt("cannot coerce %s to a string", "a Boolean"),
hintfmt("while evaluating the first argument passed to builtins.storePath"));
hintfmt("while evaluating the argument of builtins.storePath"));

}

Expand Down