diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index 1728abfd9..91aa910a2 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -539,6 +539,21 @@ flag, e.g. --option gc-keep-outputs false.
+ restrict-eval
+
+
+
+ If set to true, the Nix evaluator will
+ not allow access to any files outside of the Nix search path (as
+ set via the NIX_PATH environment variable or the
+ option). The default is
+ false.
+
+
+
+
+
+
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 95b56e84d..d82d1d6aa 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -195,6 +195,8 @@ EvalState::EvalState(const Strings & _searchPath)
nrListConcats = nrPrimOpCalls = nrFunctionCalls = 0;
countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0";
+ restricted = settings.get("restrict-eval", false);
+
#if HAVE_BOEHMGC
static bool gcInitialised = false;
if (!gcInitialised) {
@@ -250,6 +252,21 @@ EvalState::~EvalState()
}
+Path EvalState::checkSourcePath(const Path & path_)
+{
+ if (!restricted) return path_;
+
+ /* Resolve symlinks. */
+ Path path = canonPath(path_, true);
+
+ for (auto & i : searchPath)
+ if (path == i.second || isInDir(path, i.second))
+ return path;
+
+ throw RestrictedPathError(format("access to path ‘%1%’ is forbidden in restricted mode") % path_);
+}
+
+
void EvalState::addConstant(const string & name, Value & v)
{
Value * v2 = allocValue();
@@ -555,7 +572,7 @@ void EvalState::evalFile(const Path & path, Value & v)
}
startNest(nest, lvlTalkative, format("evaluating file ‘%1%’") % path2);
- Expr * e = parseExprFromFile(path2);
+ Expr * e = parseExprFromFile(checkSourcePath(path2));
try {
eval(e, v);
} catch (Error & e) {
@@ -1358,8 +1375,8 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
dstPath = srcToStore[path];
else {
dstPath = settings.readOnlyMode
- ? computeStorePathForPath(path).first
- : store->addToStore(path, true, htSHA256, defaultPathFilter, repair);
+ ? computeStorePathForPath(checkSourcePath(path)).first
+ : store->addToStore(checkSourcePath(path), true, htSHA256, defaultPathFilter, repair);
srcToStore[path] = dstPath;
printMsg(lvlChatty, format("copied source ‘%1%’ -> ‘%2%’")
% path % dstPath);
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index f7415fb78..bfaa4081d 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -135,6 +135,10 @@ public:
already exist there. */
bool repair;
+ /* If set, don't allow access to files outside of the Nix search
+ path or to environment variables. */
+ bool restricted;
+
private:
SrcToStore srcToStore;
@@ -155,6 +159,8 @@ public:
void addToSearchPath(const string & s, bool warn = false);
+ Path checkSourcePath(const Path & path);
+
/* Parse a Nix expression from the specified file. */
Expr * parseExprFromFile(const Path & path);
Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv);
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 121dc58f2..ef07d4557 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -16,6 +16,7 @@ MakeError(ThrownError, AssertionError)
MakeError(Abort, EvalError)
MakeError(TypeError, EvalError)
MakeError(UndefinedVarError, Error)
+MakeError(RestrictedPathError, Error)
/* Position objects. */
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index d70d29be8..664d6692f 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -614,7 +614,8 @@ void EvalState::addToSearchPath(const string & s, bool warn)
path = absPath(path);
if (pathExists(path)) {
debug(format("adding path ‘%1%’ to the search path") % path);
- searchPath.push_back(std::pair(prefix, path));
+ /* Resolve symlinks in the path to support restricted mode. */
+ searchPath.push_back(std::pair(prefix, canonPath(path, true)));
} else if (warn)
printMsg(lvlError, format("warning: Nix search path entry ‘%1%’ does not exist, ignoring") % path);
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index a4efd397e..a4363c678 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -80,6 +80,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
% path % e.path % pos);
}
+ path = state.checkSourcePath(path);
+
if (isStorePath(path) && store->isValidPath(path) && isDerivation(path)) {
Derivation drv = readDerivation(path);
Value & w = *state.allocValue();
@@ -133,7 +135,7 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
/* !!! Should we pass the Pos or the file name too? */
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
-/* Load a ValueInitializer from a dso and return whatever it initializes */
+/* Load a ValueInitializer from a DSO and return whatever it initializes */
static void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
@@ -146,6 +148,8 @@ static void prim_importNative(EvalState & state, const Pos & pos, Value * * args
% path % e.path % pos);
}
+ path = state.checkSourcePath(path);
+
string sym = state.forceStringNoCtx(*args[1], pos);
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
@@ -380,7 +384,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, getEnv(name));
+ mkString(v, state.restricted ? "" : getEnv(name));
}
@@ -680,7 +684,7 @@ static void prim_toPath(EvalState & state, const Pos & pos, Value * * args, Valu
static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v)
{
PathSet context;
- Path path = state.coerceToPath(pos, *args[0], context);
+ Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context));
/* 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. */
@@ -701,7 +705,15 @@ static void prim_pathExists(EvalState & state, const Pos & pos, Value * * args,
Path path = state.coerceToPath(pos, *args[0], context);
if (!context.empty())
throw EvalError(format("string ‘%1%’ cannot refer to other paths, at %2%") % path % pos);
- mkBool(v, pathExists(path));
+ try {
+ mkBool(v, pathExists(state.checkSourcePath(path)));
+ } catch (SysError & e) {
+ /* Don't give away info from errors while canonicalising
+ ‘path’ in restricted mode. */
+ mkBool(v, false);
+ } catch (RestrictedPathError & e) {
+ mkBool(v, false);
+ }
}
@@ -736,7 +748,7 @@ static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Va
throw EvalError(format("cannot read ‘%1%’, since path ‘%2%’ is not valid, at %3%")
% path % e.path % pos);
}
- mkString(v, readFile(path).c_str());
+ mkString(v, readFile(state.checkSourcePath(path)).c_str());
}
@@ -763,7 +775,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
throw EvalError(format("attribute ‘path’ missing, at %1%") % pos);
string path = state.coerceToPath(pos, *i->value, context);
- searchPath.push_back(std::pair(prefix, path));
+ searchPath.push_back(std::pair(prefix, state.checkSourcePath(path)));
}
string path = state.forceStringNoCtx(*args[1], pos);
@@ -790,7 +802,7 @@ static void prim_readDir(EvalState & state, const Pos & pos, Value * * args, Val
% path % e.path % pos);
}
- DirEntries entries = readDirectory(path);
+ DirEntries entries = readDirectory(state.checkSourcePath(path));
state.mkAttrs(v, entries.size());
for (auto & ent : entries) {
@@ -927,6 +939,8 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
FilterFromExpr filter(state, *args[0]);
+ path = state.checkSourcePath(path);
+
Path dstPath = settings.readOnlyMode
? computeStorePathForPath(path, true, htSHA256, filter).first
: store->addToStore(path, true, htSHA256, filter, state.repair);