Add restricted evaluation mode
If ‘--option restrict-eval true’ is given, the evaluator will throw an exception if an attempt is made to access any file outside of the Nix search path. This is primarily intended for Hydra, where we don't want people doing ‘builtins.readFile ~/.ssh/id_dsa’ or stuff like that.
This commit is contained in:
parent
47bdc52c1b
commit
15d2d3c34e
|
@ -539,6 +539,21 @@ flag, e.g. <literal>--option gc-keep-outputs false</literal>.</para>
|
||||||
</varlistentry>
|
</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>
|
</variablelist>
|
||||||
|
|
||||||
</para>
|
</para>
|
||||||
|
|
|
@ -195,6 +195,8 @@ EvalState::EvalState(const Strings & _searchPath)
|
||||||
nrListConcats = nrPrimOpCalls = nrFunctionCalls = 0;
|
nrListConcats = nrPrimOpCalls = nrFunctionCalls = 0;
|
||||||
countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0";
|
countCalls = getEnv("NIX_COUNT_CALLS", "0") != "0";
|
||||||
|
|
||||||
|
restricted = settings.get("restrict-eval", false);
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
static bool gcInitialised = false;
|
static bool gcInitialised = false;
|
||||||
if (!gcInitialised) {
|
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)
|
void EvalState::addConstant(const string & name, Value & v)
|
||||||
{
|
{
|
||||||
Value * v2 = allocValue();
|
Value * v2 = allocValue();
|
||||||
|
@ -555,7 +572,7 @@ void EvalState::evalFile(const Path & path, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
startNest(nest, lvlTalkative, format("evaluating file ‘%1%’") % path2);
|
startNest(nest, lvlTalkative, format("evaluating file ‘%1%’") % path2);
|
||||||
Expr * e = parseExprFromFile(path2);
|
Expr * e = parseExprFromFile(checkSourcePath(path2));
|
||||||
try {
|
try {
|
||||||
eval(e, v);
|
eval(e, v);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
@ -1358,8 +1375,8 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
dstPath = srcToStore[path];
|
dstPath = srcToStore[path];
|
||||||
else {
|
else {
|
||||||
dstPath = settings.readOnlyMode
|
dstPath = settings.readOnlyMode
|
||||||
? computeStorePathForPath(path).first
|
? computeStorePathForPath(checkSourcePath(path)).first
|
||||||
: store->addToStore(path, true, htSHA256, defaultPathFilter, repair);
|
: store->addToStore(checkSourcePath(path), true, htSHA256, defaultPathFilter, repair);
|
||||||
srcToStore[path] = dstPath;
|
srcToStore[path] = dstPath;
|
||||||
printMsg(lvlChatty, format("copied source ‘%1%’ -> ‘%2%’")
|
printMsg(lvlChatty, format("copied source ‘%1%’ -> ‘%2%’")
|
||||||
% path % dstPath);
|
% path % dstPath);
|
||||||
|
|
|
@ -135,6 +135,10 @@ public:
|
||||||
already exist there. */
|
already exist there. */
|
||||||
bool repair;
|
bool repair;
|
||||||
|
|
||||||
|
/* If set, don't allow access to files outside of the Nix search
|
||||||
|
path or to environment variables. */
|
||||||
|
bool restricted;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SrcToStore srcToStore;
|
SrcToStore srcToStore;
|
||||||
|
|
||||||
|
@ -155,6 +159,8 @@ public:
|
||||||
|
|
||||||
void addToSearchPath(const string & s, bool warn = false);
|
void addToSearchPath(const string & s, bool warn = false);
|
||||||
|
|
||||||
|
Path checkSourcePath(const Path & path);
|
||||||
|
|
||||||
/* Parse a Nix expression from the specified file. */
|
/* Parse a Nix expression from the specified file. */
|
||||||
Expr * parseExprFromFile(const Path & path);
|
Expr * parseExprFromFile(const Path & path);
|
||||||
Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv);
|
Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv);
|
||||||
|
|
|
@ -16,6 +16,7 @@ MakeError(ThrownError, AssertionError)
|
||||||
MakeError(Abort, EvalError)
|
MakeError(Abort, EvalError)
|
||||||
MakeError(TypeError, EvalError)
|
MakeError(TypeError, EvalError)
|
||||||
MakeError(UndefinedVarError, Error)
|
MakeError(UndefinedVarError, Error)
|
||||||
|
MakeError(RestrictedPathError, Error)
|
||||||
|
|
||||||
|
|
||||||
/* Position objects. */
|
/* Position objects. */
|
||||||
|
|
|
@ -614,7 +614,8 @@ void EvalState::addToSearchPath(const string & s, bool warn)
|
||||||
path = absPath(path);
|
path = absPath(path);
|
||||||
if (pathExists(path)) {
|
if (pathExists(path)) {
|
||||||
debug(format("adding path ‘%1%’ to the search path") % 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)
|
} else if (warn)
|
||||||
printMsg(lvlError, format("warning: Nix search path entry ‘%1%’ does not exist, ignoring") % path);
|
printMsg(lvlError, format("warning: Nix search path entry ‘%1%’ does not exist, ignoring") % path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,8 @@ static void prim_scopedImport(EvalState & state, const Pos & pos, Value * * args
|
||||||
% path % e.path % pos);
|
% path % e.path % pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path = state.checkSourcePath(path);
|
||||||
|
|
||||||
if (isStorePath(path) && store->isValidPath(path) && isDerivation(path)) {
|
if (isStorePath(path) && store->isValidPath(path) && isDerivation(path)) {
|
||||||
Derivation drv = readDerivation(path);
|
Derivation drv = readDerivation(path);
|
||||||
Value & w = *state.allocValue();
|
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? */
|
/* !!! Should we pass the Pos or the file name too? */
|
||||||
extern "C" typedef void (*ValueInitializer)(EvalState & state, Value & v);
|
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)
|
static void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
@ -146,6 +148,8 @@ static void prim_importNative(EvalState & state, const Pos & pos, Value * * args
|
||||||
% path % e.path % pos);
|
% path % e.path % pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path = state.checkSourcePath(path);
|
||||||
|
|
||||||
string sym = state.forceStringNoCtx(*args[1], pos);
|
string sym = state.forceStringNoCtx(*args[1], pos);
|
||||||
|
|
||||||
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
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)
|
static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
string name = state.forceStringNoCtx(*args[0], pos);
|
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)
|
static void prim_storePath(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
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
|
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
|
||||||
directly in the store. The latter condition is necessary so
|
directly in the store. The latter condition is necessary so
|
||||||
e.g. nix-push does the right thing. */
|
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);
|
Path path = state.coerceToPath(pos, *args[0], context);
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
throw EvalError(format("string ‘%1%’ cannot refer to other paths, at %2%") % path % pos);
|
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%")
|
throw EvalError(format("cannot read ‘%1%’, since path ‘%2%’ is not valid, at %3%")
|
||||||
% path % e.path % pos);
|
% 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);
|
throw EvalError(format("attribute ‘path’ missing, at %1%") % pos);
|
||||||
string path = state.coerceToPath(pos, *i->value, context);
|
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);
|
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);
|
% path % e.path % pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
DirEntries entries = readDirectory(path);
|
DirEntries entries = readDirectory(state.checkSourcePath(path));
|
||||||
state.mkAttrs(v, entries.size());
|
state.mkAttrs(v, entries.size());
|
||||||
|
|
||||||
for (auto & ent : entries) {
|
for (auto & ent : entries) {
|
||||||
|
@ -927,6 +939,8 @@ static void prim_filterSource(EvalState & state, const Pos & pos, Value * * args
|
||||||
|
|
||||||
FilterFromExpr filter(state, *args[0]);
|
FilterFromExpr filter(state, *args[0]);
|
||||||
|
|
||||||
|
path = state.checkSourcePath(path);
|
||||||
|
|
||||||
Path dstPath = settings.readOnlyMode
|
Path dstPath = settings.readOnlyMode
|
||||||
? computeStorePathForPath(path, true, htSHA256, filter).first
|
? computeStorePathForPath(path, true, htSHA256, filter).first
|
||||||
: store->addToStore(path, true, htSHA256, filter, state.repair);
|
: store->addToStore(path, true, htSHA256, filter, state.repair);
|
||||||
|
|
Loading…
Reference in a new issue