about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/command-ref/conf-file.xml15
-rw-r--r--src/libexpr/eval.cc23
-rw-r--r--src/libexpr/eval.hh6
-rw-r--r--src/libexpr/nixexpr.hh1
-rw-r--r--src/libexpr/parser.y3
-rw-r--r--src/libexpr/primops.cc28
6 files changed, 65 insertions, 11 deletions
diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml
index 1728abfd9c59..91aa910a2946 100644
--- a/doc/manual/command-ref/conf-file.xml
+++ b/doc/manual/command-ref/conf-file.xml
@@ -539,6 +539,21 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
   </varlistentry>
 
 
+  <varlistentry xml:id="conf-restrict-eval"><term><literal>restrict-eval</literal></term>
+
+    <listitem>
+
+      <para>If set to <literal>true</literal>, the Nix evaluator will
+      not allow access to any files outside of the Nix search path (as
+      set via the <envar>NIX_PATH</envar> environment variable or the
+      <option>-I</option> option). The default is
+      <literal>false</literal>.</para>
+
+    </listitem>
+
+  </varlistentry>
+
+
 </variablelist>
 
 </para>
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index 95b56e84d89a..d82d1d6aafa2 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 f7415fb78dfd..bfaa4081d488 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 121dc58f25f7..ef07d4557fe8 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 d70d29be8ba7..664d6692f51e 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<string, Path>(prefix, path));
+        /* Resolve symlinks in the path to support restricted mode. */
+        searchPath.push_back(std::pair<string, Path>(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 a4efd397ec7a..a4363c678a16 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<string, Path>(prefix, path));
+        searchPath.push_back(std::pair<string, Path>(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);