aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mk/tests.mk2
-rw-r--r--release.nix2
-rw-r--r--src/libexpr/eval.cc65
-rw-r--r--src/libexpr/eval.hh8
-rw-r--r--src/libexpr/primops.cc44
-rw-r--r--src/libexpr/primops/fetchGit.cc15
-rw-r--r--src/libexpr/primops/fetchMercurial.cc6
-rw-r--r--src/libstore/globals.hh3
-rw-r--r--src/libutil/util.cc6
-rw-r--r--src/libutil/util.hh6
-rwxr-xr-xsrc/nix-build/nix-build.cc4
-rw-r--r--src/nix-instantiate/nix-instantiate.cc2
-rw-r--r--tests/fetchGit.sh7
-rw-r--r--tests/fetchMercurial.sh7
-rw-r--r--tests/local.mk3
-rw-r--r--tests/pure-eval.nix3
-rw-r--r--tests/pure-eval.sh18
-rw-r--r--tests/restricted.nix1
-rw-r--r--tests/restricted.sh10
19 files changed, 159 insertions, 53 deletions
diff --git a/mk/tests.mk b/mk/tests.mk
index e353d46a0..70c30661b 100644
--- a/mk/tests.mk
+++ b/mk/tests.mk
@@ -39,7 +39,7 @@ installcheck:
echo "$${red}$$failed out of $$total tests failed $$normal"; \
exit 1; \
else \
- echo "$${green}All tests succeeded"; \
+ echo "$${green}All tests succeeded$$normal"; \
fi
.PHONY: check installcheck
diff --git a/release.nix b/release.nix
index 30bec2a6e..04f1f8367 100644
--- a/release.nix
+++ b/release.nix
@@ -6,7 +6,7 @@
let
- pkgs = import nixpkgs {};
+ pkgs = import nixpkgs { system = "x86_64-linux"; };
jobs = rec {
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 087a95dde..f8685e010 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -300,16 +300,25 @@ EvalState::EvalState(const Strings & _searchPath, ref<Store> store)
{
countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0";
- restricted = settings.restrictEval;
-
assert(gcInitialised);
/* Initialise the Nix expression search path. */
- Strings paths = parseNixPath(getEnv("NIX_PATH", ""));
- for (auto & i : _searchPath) addToSearchPath(i);
- for (auto & i : paths) addToSearchPath(i);
+ if (!settings.pureEval) {
+ Strings paths = parseNixPath(getEnv("NIX_PATH", ""));
+ for (auto & i : _searchPath) addToSearchPath(i);
+ for (auto & i : paths) addToSearchPath(i);
+ }
addToSearchPath("nix=" + settings.nixDataDir + "/nix/corepkgs");
+ if (settings.restrictEval || settings.pureEval) {
+ allowedPaths = PathSet();
+ for (auto & i : searchPath) {
+ auto r = resolveSearchPathElem(i);
+ if (!r.first) continue;
+ allowedPaths->insert(r.second);
+ }
+ }
+
clearValue(vEmptySet);
vEmptySet.type = tAttrs;
vEmptySet.attrs = allocBindings(0);
@@ -326,38 +335,39 @@ EvalState::~EvalState()
Path EvalState::checkSourcePath(const Path & path_)
{
- if (!restricted) return path_;
+ if (!allowedPaths) return path_;
+
+ auto doThrow = [&]() [[noreturn]] {
+ throw RestrictedPathError("access to path '%1%' is forbidden in restricted mode", path_);
+ };
+
+ bool found = false;
+
+ for (auto & i : *allowedPaths) {
+ if (isDirOrInDir(path_, i)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) doThrow();
/* Resolve symlinks. */
debug(format("checking access to '%s'") % path_);
Path path = canonPath(path_, true);
- for (auto & i : searchPath) {
- auto r = resolveSearchPathElem(i);
- if (!r.first) continue;
- if (path == r.second || isInDir(path, r.second))
+ for (auto & i : *allowedPaths) {
+ if (isDirOrInDir(path, i))
return path;
}
- /* To support import-from-derivation, allow access to anything in
- the store. FIXME: only allow access to paths that have been
- constructed by this evaluation. */
- if (store->isInStore(path)) return path;
-
-#if 0
- /* Hack to support the chroot dependencies of corepkgs (see
- corepkgs/config.nix.in). */
- if (path == settings.nixPrefix && isStorePath(settings.nixPrefix))
- return path;
-#endif
-
- throw RestrictedPathError(format("access to path '%1%' is forbidden in restricted mode") % path_);
+ doThrow();
}
void EvalState::checkURI(const std::string & uri)
{
- if (!restricted) return;
+ if (!settings.restrictEval) return;
/* 'uri' should be equal to a prefix, or in a subdirectory of a
prefix. Thus, the prefix https://github.co does not permit
@@ -396,7 +406,7 @@ void EvalState::addConstant(const string & name, Value & v)
}
-void EvalState::addPrimOp(const string & name,
+Value * EvalState::addPrimOp(const string & name,
unsigned int arity, PrimOpFun primOp)
{
Value * v = allocValue();
@@ -407,6 +417,7 @@ void EvalState::addPrimOp(const string & name,
staticBaseEnv.vars[symbols.create(name)] = baseEnvDispl;
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
+ return v;
}
@@ -659,8 +670,10 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
}
-void EvalState::evalFile(const Path & path, Value & v)
+void EvalState::evalFile(const Path & path_, Value & v)
{
+ auto path = checkSourcePath(path_);
+
FileEvalCache::iterator i;
if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) {
v = i->second;
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index cc971ae80..9e3d30d95 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -76,9 +76,9 @@ public:
already exist there. */
RepairFlag repair;
- /* If set, don't allow access to files outside of the Nix search
- path or to environment variables. */
- bool restricted;
+ /* The allowed filesystem paths in restricted or pure evaluation
+ mode. */
+ std::experimental::optional<PathSet> allowedPaths;
Value vEmptySet;
@@ -212,7 +212,7 @@ private:
void addConstant(const string & name, Value & v);
- void addPrimOp(const string & name,
+ Value * addPrimOp(const string & name,
unsigned int arity, PrimOpFun primOp);
public:
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 98fe2199e..0ec035b86 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -439,7 +439,7 @@ static void prim_tryEval(EvalState & state, const Pos & pos, Value * * args, Val
static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
string name = state.forceStringNoCtx(*args[0], pos);
- mkString(v, state.restricted ? "" : getEnv(name));
+ mkString(v, settings.restrictEval || settings.pureEval ? "" : getEnv(name));
}
@@ -1929,7 +1929,14 @@ void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
state.checkURI(url);
+ if (settings.pureEval && !expectedHash)
+ throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who);
+
Path res = getDownloader()->downloadCached(state.store, url, unpack, name, expectedHash);
+
+ if (state.allowedPaths)
+ state.allowedPaths->insert(res);
+
mkString(v, res, PathSet({res}));
}
@@ -1981,11 +1988,28 @@ void EvalState::createBaseEnv()
mkNull(v);
addConstant("null", v);
- mkInt(v, time(0));
- addConstant("__currentTime", v);
+ auto vThrow = addPrimOp("throw", 1, prim_throw);
- mkString(v, settings.thisSystem);
- addConstant("__currentSystem", v);
+ auto addPurityError = [&](const std::string & name) {
+ Value * v2 = allocValue();
+ mkString(*v2, fmt("'%s' is not allowed in pure evaluation mode", name));
+ mkApp(v, *vThrow, *v2);
+ addConstant(name, v);
+ };
+
+ if (settings.pureEval)
+ addPurityError("__currentTime");
+ else {
+ mkInt(v, time(0));
+ addConstant("__currentTime", v);
+ }
+
+ if (settings.pureEval)
+ addPurityError("__currentSystem");
+ else {
+ mkString(v, settings.thisSystem);
+ addConstant("__currentSystem", v);
+ }
mkString(v, nixVersion);
addConstant("__nixVersion", v);
@@ -2001,10 +2025,10 @@ void EvalState::createBaseEnv()
addConstant("__langVersion", v);
// Miscellaneous
- addPrimOp("scopedImport", 2, prim_scopedImport);
+ auto vScopedImport = addPrimOp("scopedImport", 2, prim_scopedImport);
Value * v2 = allocValue();
mkAttrs(*v2, 0);
- mkApp(v, *baseEnv.values[baseEnvDispl - 1], *v2);
+ mkApp(v, *vScopedImport, *v2);
forceValue(v);
addConstant("import", v);
if (settings.enableNativeCode) {
@@ -2020,7 +2044,6 @@ void EvalState::createBaseEnv()
addPrimOp("__isBool", 1, prim_isBool);
addPrimOp("__genericClosure", 1, prim_genericClosure);
addPrimOp("abort", 1, prim_abort);
- addPrimOp("throw", 1, prim_throw);
addPrimOp("__addErrorContext", 2, prim_addErrorContext);
addPrimOp("__tryEval", 1, prim_tryEval);
addPrimOp("__getEnv", 1, prim_getEnv);
@@ -2035,7 +2058,10 @@ void EvalState::createBaseEnv()
// Paths
addPrimOp("__toPath", 1, prim_toPath);
- addPrimOp("__storePath", 1, prim_storePath);
+ if (settings.pureEval)
+ addPurityError("__storePath");
+ else
+ addPrimOp("__storePath", 1, prim_storePath);
addPrimOp("__pathExists", 1, prim_pathExists);
addPrimOp("baseNameOf", 1, prim_baseNameOf);
addPrimOp("dirOf", 1, prim_dirOf);
diff --git a/src/libexpr/primops/fetchGit.cc b/src/libexpr/primops/fetchGit.cc
index fb664cffb..2e3e2634d 100644
--- a/src/libexpr/primops/fetchGit.cc
+++ b/src/libexpr/primops/fetchGit.cc
@@ -22,10 +22,15 @@ struct GitInfo
uint64_t revCount = 0;
};
+std::regex revRegex("^[0-9a-fA-F]{40}$");
+
GitInfo exportGit(ref<Store> store, const std::string & uri,
std::experimental::optional<std::string> ref, std::string rev,
const std::string & name)
{
+ if (settings.pureEval && rev == "")
+ throw Error("in pure evaluation mode, 'fetchGit' requires a Git revision");
+
if (!ref && rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.git")) {
bool clean = true;
@@ -76,11 +81,8 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
if (!ref) ref = "master"s;
- if (rev != "") {
- std::regex revRegex("^[0-9a-fA-F]{40}$");
- if (!std::regex_match(rev, revRegex))
- throw Error("invalid Git revision '%s'", rev);
- }
+ if (rev != "" && !std::regex_match(rev, revRegex))
+ throw Error("invalid Git revision '%s'", rev);
Path cacheDir = getCacheDir() + "/nix/git";
@@ -231,6 +233,9 @@ static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Va
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), gitInfo.shortRev);
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), gitInfo.revCount);
v.attrs->sort();
+
+ if (state.allowedPaths)
+ state.allowedPaths->insert(gitInfo.storePath);
}
static RegisterPrimOp r("fetchGit", 1, prim_fetchGit);
diff --git a/src/libexpr/primops/fetchMercurial.cc b/src/libexpr/primops/fetchMercurial.cc
index a317476c5..5517d83df 100644
--- a/src/libexpr/primops/fetchMercurial.cc
+++ b/src/libexpr/primops/fetchMercurial.cc
@@ -27,6 +27,9 @@ std::regex commitHashRegex("^[0-9a-fA-F]{40}$");
HgInfo exportMercurial(ref<Store> store, const std::string & uri,
std::string rev, const std::string & name)
{
+ if (settings.pureEval && rev == "")
+ throw Error("in pure evaluation mode, 'fetchMercurial' requires a Mercurial revision");
+
if (rev == "" && hasPrefix(uri, "/") && pathExists(uri + "/.hg")) {
bool clean = runProgram("hg", true, { "status", "-R", uri, "--modified", "--added", "--removed" }) == "";
@@ -196,6 +199,9 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
mkString(*state.allocAttr(v, state.symbols.create("shortRev")), std::string(hgInfo.rev, 0, 12));
mkInt(*state.allocAttr(v, state.symbols.create("revCount")), hgInfo.revCount);
v.attrs->sort();
+
+ if (state.allowedPaths)
+ state.allowedPaths->insert(hgInfo.storePath);
}
static RegisterPrimOp r("fetchMercurial", 1, prim_fetchMercurial);
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index af72f7b1e..81bb24a4e 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -232,6 +232,9 @@ public:
"Whether to restrict file system access to paths in $NIX_PATH, "
"and network access to the URI prefixes listed in 'allowed-uris'."};
+ Setting<bool> pureEval{this, false, "pure-eval",
+ "Whether to restrict file system and network access to files specified by cryptographic hash."};
+
Setting<size_t> buildRepeat{this, 0, "repeat",
"The number of times to repeat a build in order to verify determinism.",
{"build-repeat"}};
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 197df0c44..272997397 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -192,6 +192,12 @@ bool isInDir(const Path & path, const Path & dir)
}
+bool isDirOrInDir(const Path & path, const Path & dir)
+{
+ return path == dir or isInDir(path, dir);
+}
+
+
struct stat lstat(const Path & path)
{
struct stat st;
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index a3494e09b..75eb97515 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -53,10 +53,12 @@ Path dirOf(const Path & path);
following the final `/'. */
string baseNameOf(const Path & path);
-/* Check whether a given path is a descendant of the given
- directory. */
+/* Check whether 'path' is a descendant of 'dir'. */
bool isInDir(const Path & path, const Path & dir);
+/* Check whether 'path' is equal to 'dir' or a descendant of 'dir'. */
+bool isDirOrInDir(const Path & path, const Path & dir);
+
/* Get status of `path'. */
struct stat lstat(const Path & path);
diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc
index 58366daa6..1b2494275 100755
--- a/src/nix-build/nix-build.cc
+++ b/src/nix-build/nix-build.cc
@@ -279,8 +279,8 @@ void mainWrapped(int argc, char * * argv)
else
/* If we're in a #! script, interpret filenames
relative to the script. */
- exprs.push_back(state.parseExprFromFile(resolveExprPath(lookupFileArg(state,
- inShebang && !packages ? absPath(i, absPath(dirOf(script))) : i))));
+ exprs.push_back(state.parseExprFromFile(resolveExprPath(state.checkSourcePath(lookupFileArg(state,
+ inShebang && !packages ? absPath(i, absPath(dirOf(script))) : i)))));
}
/* Evaluate them into derivations. */
diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc
index 55ac007e8..e05040a42 100644
--- a/src/nix-instantiate/nix-instantiate.cc
+++ b/src/nix-instantiate/nix-instantiate.cc
@@ -182,7 +182,7 @@ int main(int argc, char * * argv)
for (auto & i : files) {
Expr * e = fromArgs
? state.parseExprFromString(i, absPath("."))
- : state.parseExprFromFile(resolveExprPath(lookupFileArg(state, i)));
+ : state.parseExprFromFile(resolveExprPath(state.checkSourcePath(lookupFileArg(state, i))));
processExpr(state, attrPaths, parseOnly, strict, autoArgs,
evalOnly, outputKind, xmlOutputSourceLocation, e);
}
diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh
index b556fe594..530ac7bb8 100644
--- a/tests/fetchGit.sh
+++ b/tests/fetchGit.sh
@@ -29,10 +29,17 @@ rev2=$(git -C $repo rev-parse HEAD)
path=$(nix eval --raw "(builtins.fetchGit file://$repo).outPath")
[[ $(cat $path/hello) = world ]]
+# In pure eval mode, fetchGit without a revision should fail.
+[[ $(nix eval --raw "(builtins.readFile (fetchGit file://$repo + \"/hello\"))") = world ]]
+(! nix eval --pure-eval --raw "(builtins.readFile (fetchGit file://$repo + \"/hello\"))")
+
# Fetch using an explicit revision hash.
path2=$(nix eval --raw "(builtins.fetchGit { url = file://$repo; rev = \"$rev2\"; }).outPath")
[[ $path = $path2 ]]
+# In pure eval mode, fetchGit with a revision should succeed.
+[[ $(nix eval --pure-eval --raw "(builtins.readFile (fetchGit { url = file://$repo; rev = \"$rev2\"; } + \"/hello\"))") = world ]]
+
# Fetch again. This should be cached.
mv $repo ${repo}-tmp
path2=$(nix eval --raw "(builtins.fetchGit file://$repo).outPath")
diff --git a/tests/fetchMercurial.sh b/tests/fetchMercurial.sh
index 271350ecd..4088dbd39 100644
--- a/tests/fetchMercurial.sh
+++ b/tests/fetchMercurial.sh
@@ -29,10 +29,17 @@ rev2=$(hg log --cwd $repo -r tip --template '{node}')
path=$(nix eval --raw "(builtins.fetchMercurial file://$repo).outPath")
[[ $(cat $path/hello) = world ]]
+# In pure eval mode, fetchGit without a revision should fail.
+[[ $(nix eval --raw "(builtins.readFile (fetchMercurial file://$repo + \"/hello\"))") = world ]]
+(! nix eval --pure-eval --raw "(builtins.readFile (fetchMercurial file://$repo + \"/hello\"))")
+
# Fetch using an explicit revision hash.
path2=$(nix eval --raw "(builtins.fetchMercurial { url = file://$repo; rev = \"$rev2\"; }).outPath")
[[ $path = $path2 ]]
+# In pure eval mode, fetchGit with a revision should succeed.
+[[ $(nix eval --pure-eval --raw "(builtins.readFile (fetchMercurial { url = file://$repo; rev = \"$rev2\"; } + \"/hello\"))") = world ]]
+
# Fetch again. This should be cached.
mv $repo ${repo}-tmp
path2=$(nix eval --raw "(builtins.fetchMercurial file://$repo).outPath")
diff --git a/tests/local.mk b/tests/local.mk
index 83154228e..82502a8e5 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -20,7 +20,8 @@ nix_tests = \
fetchMercurial.sh \
signing.sh \
run.sh \
- brotli.sh
+ brotli.sh \
+ pure-eval.sh
# parallel.sh
install-tests += $(foreach x, $(nix_tests), tests/$(x))
diff --git a/tests/pure-eval.nix b/tests/pure-eval.nix
new file mode 100644
index 000000000..ed25b3d45
--- /dev/null
+++ b/tests/pure-eval.nix
@@ -0,0 +1,3 @@
+{
+ x = 123;
+}
diff --git a/tests/pure-eval.sh b/tests/pure-eval.sh
new file mode 100644
index 000000000..49c856448
--- /dev/null
+++ b/tests/pure-eval.sh
@@ -0,0 +1,18 @@
+source common.sh
+
+clearStore
+
+nix eval --pure-eval '(assert 1 + 2 == 3; true)'
+
+[[ $(nix eval '(builtins.readFile ./pure-eval.sh)') =~ clearStore ]]
+
+(! nix eval --pure-eval '(builtins.readFile ./pure-eval.sh)')
+
+(! nix eval --pure-eval '(builtins.currentTime)')
+(! nix eval --pure-eval '(builtins.currentSystem)')
+
+(! nix-instantiate --pure-eval ./simple.nix)
+
+[[ $(nix eval "((import (builtins.fetchurl { url = file://$(pwd)/pure-eval.nix; })).x)") == 123 ]]
+(! nix eval --pure-eval "((import (builtins.fetchurl { url = file://$(pwd)/pure-eval.nix; })).x)")
+nix eval --pure-eval "((import (builtins.fetchurl { url = file://$(pwd)/pure-eval.nix; sha256 = \"$(nix hash-file pure-eval.nix --type sha256)\"; })).x)"
diff --git a/tests/restricted.nix b/tests/restricted.nix
new file mode 100644
index 000000000..e0ef58402
--- /dev/null
+++ b/tests/restricted.nix
@@ -0,0 +1 @@
+1 + 2
diff --git a/tests/restricted.sh b/tests/restricted.sh
index c063c8693..6c0392fac 100644
--- a/tests/restricted.sh
+++ b/tests/restricted.sh
@@ -3,7 +3,8 @@ source common.sh
clearStore
nix-instantiate --restrict-eval --eval -E '1 + 2'
-(! nix-instantiate --restrict-eval ./simple.nix)
+(! nix-instantiate --restrict-eval ./restricted.nix)
+(! nix-instantiate --eval --restrict-eval <(echo '1 + 2'))
nix-instantiate --restrict-eval ./simple.nix -I src=.
nix-instantiate --restrict-eval ./simple.nix -I src1=simple.nix -I src2=config.nix -I src3=./simple.builder.sh
@@ -28,3 +29,10 @@ nix eval --raw "(builtins.fetchurl file://$(pwd)/restricted.sh)" --restrict-eval
(! nix eval --raw "(builtins.fetchurl https://github.com/NixOS/patchelf/archive/master.tar.gz)" --restrict-eval)
(! nix eval --raw "(builtins.fetchTarball https://github.com/NixOS/patchelf/archive/master.tar.gz)" --restrict-eval)
(! nix eval --raw "(fetchGit git://github.com/NixOS/patchelf.git)" --restrict-eval)
+
+ln -sfn $(pwd)/restricted.nix $TEST_ROOT/restricted.nix
+[[ $(nix-instantiate --eval $TEST_ROOT/restricted.nix) == 3 ]]
+(! nix-instantiate --eval --restrict-eval $TEST_ROOT/restricted.nix)
+(! nix-instantiate --eval --restrict-eval $TEST_ROOT/restricted.nix -I $TEST_ROOT)
+(! nix-instantiate --eval --restrict-eval $TEST_ROOT/restricted.nix -I .)
+nix-instantiate --eval --restrict-eval $TEST_ROOT/restricted.nix -I $TEST_ROOT -I .