libutil: add CheckedSourcePath for accessing things
SourcePath only manipulates path names now. all accesses must go through a checked path going forward to ensure we don't escape restriction lists of pure and restricted evaluation. if a directory path is checked it can safely be assumed that the directory itself is allowed, and its contents will likewise be safe to access. it is tempting to assumed that contents will also be fine, but that's only true if the content is not a symlink. Change-Id: Icec3098d53fe9dce50997954ba958fe4f304d59b
This commit is contained in:
committed by
Jade Lovelace
parent
8db8ac9a67
commit
c948b350fb
@@ -96,7 +96,7 @@ static bool parseInstallSourceOptions(Globals & globals,
|
||||
}
|
||||
|
||||
|
||||
static bool isNixExpr(EvalPaths & paths, const SourcePath & path, struct InputAccessor::Stat & st)
|
||||
static bool isNixExpr(EvalPaths & paths, const CheckedSourcePath & path, struct InputAccessor::Stat & st)
|
||||
{
|
||||
if (st.type == InputAccessor::tRegular) {
|
||||
return true;
|
||||
@@ -113,7 +113,7 @@ static constexpr size_t maxAttrs = 1024;
|
||||
|
||||
|
||||
static void getAllExprs(Evaluator & state,
|
||||
const SourcePath & path, StringSet & seen, BindingsBuilder & attrs)
|
||||
const CheckedSourcePath & path, StringSet & seen, BindingsBuilder & attrs)
|
||||
{
|
||||
StringSet namesSorted;
|
||||
for (auto & [name, _] : path.readDirectory()) namesSorted.insert(name);
|
||||
@@ -124,7 +124,7 @@ static void getAllExprs(Evaluator & state,
|
||||
are implemented using profiles). */
|
||||
if (i == "manifest.nix") continue;
|
||||
|
||||
SourcePath path2 = state.paths.checkSourcePath(path + i);
|
||||
auto path2 = state.paths.checkSourcePath(path + i);
|
||||
|
||||
InputAccessor::Stat st;
|
||||
try {
|
||||
|
||||
+2
-2
@@ -655,7 +655,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||
return {path, 0};
|
||||
} else if (v.isLambda()) {
|
||||
auto pos = evaluator.positions[v.lambda.fun->pos];
|
||||
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
|
||||
return {*path, pos.line};
|
||||
else
|
||||
throw Error("'%s' cannot be shown in an editor", pos);
|
||||
@@ -820,7 +820,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
|
||||
logger->cout(trim(renderMarkdownToTerminal(markdown)));
|
||||
} else if (v.isLambda()) {
|
||||
auto pos = evaluator.positions[v.lambda.fun->pos];
|
||||
if (auto path = std::get_if<SourcePath>(&pos.origin)) {
|
||||
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin)) {
|
||||
// Path and position have now been obtained, feed to nix-doc library to get data.
|
||||
auto docComment = lambdaDocsForPos(*path, pos);
|
||||
if (!docComment) {
|
||||
|
||||
+14
-11
@@ -379,9 +379,9 @@ void EvalPaths::allowAndSetStorePathString(const StorePath & storePath, Value &
|
||||
mkStorePathString(storePath, v);
|
||||
}
|
||||
|
||||
SourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
|
||||
CheckedSourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
|
||||
{
|
||||
if (!allowedPaths) return path_;
|
||||
if (!allowedPaths) return auto(path_).unsafeIntoChecked();
|
||||
|
||||
auto i = resolvedPaths.find(path_.canonical().abs());
|
||||
if (i != resolvedPaths.end())
|
||||
@@ -395,7 +395,9 @@ SourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
|
||||
*/
|
||||
Path abspath = canonPath(path_.canonical().abs());
|
||||
|
||||
if (abspath.starts_with(corepkgsPrefix)) return CanonPath(abspath);
|
||||
if (abspath.starts_with(corepkgsPrefix)) {
|
||||
return SourcePath(CanonPath(abspath)).unsafeIntoChecked();
|
||||
}
|
||||
|
||||
for (auto & i : *allowedPaths) {
|
||||
if (isDirOrInDir(abspath, i)) {
|
||||
@@ -417,8 +419,9 @@ SourcePath EvalPaths::checkSourcePath(const SourcePath & path_)
|
||||
|
||||
for (auto & i : *allowedPaths) {
|
||||
if (isDirOrInDir(path.canonical().abs(), i)) {
|
||||
resolvedPaths.insert_or_assign(path_.canonical().abs(), path);
|
||||
return path;
|
||||
auto checked = path.unsafeIntoChecked();
|
||||
resolvedPaths.insert_or_assign(path_.canonical().abs(), checked);
|
||||
return checked;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +806,7 @@ void Evaluator::evalLazily(Expr & e, Value & v)
|
||||
void EvalState::mkPos(Value & v, PosIdx p)
|
||||
{
|
||||
auto origin = ctx.positions.originOf(p);
|
||||
if (auto path = std::get_if<SourcePath>(&origin)) {
|
||||
if (auto path = std::get_if<CheckedSourcePath>(&origin)) {
|
||||
auto attrs = ctx.buildBindings(3);
|
||||
attrs.alloc(ctx.s.file).mkString(path->to_string());
|
||||
makePositionThunks(*this, p, attrs.alloc(ctx.s.line), attrs.alloc(ctx.s.column));
|
||||
@@ -2621,7 +2624,7 @@ void Evaluator::printStatistics()
|
||||
else
|
||||
obj["name"] = nullptr;
|
||||
if (auto pos = positions[fun->pos]) {
|
||||
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
|
||||
obj["file"] = path->to_string();
|
||||
obj["line"] = pos.line;
|
||||
obj["column"] = pos.column;
|
||||
@@ -2636,7 +2639,7 @@ void Evaluator::printStatistics()
|
||||
for (auto & i : stats.attrSelects) {
|
||||
json obj = json::object();
|
||||
if (auto pos = positions[i.first]) {
|
||||
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
|
||||
obj["file"] = path->to_string();
|
||||
obj["line"] = pos.line;
|
||||
obj["column"] = pos.column;
|
||||
@@ -2661,7 +2664,7 @@ void Evaluator::printStatistics()
|
||||
}
|
||||
|
||||
|
||||
SourcePath EvalPaths::resolveExprPath(SourcePath path_)
|
||||
CheckedSourcePath EvalPaths::resolveExprPath(SourcePath path_)
|
||||
{
|
||||
auto path = checkSourcePath(path_);
|
||||
unsigned int followCount = 0, maxFollow = 1024;
|
||||
@@ -2686,13 +2689,13 @@ SourcePath EvalPaths::resolveExprPath(SourcePath path_)
|
||||
}
|
||||
|
||||
|
||||
Expr & Evaluator::parseExprFromFile(const SourcePath & path)
|
||||
Expr & Evaluator::parseExprFromFile(const CheckedSourcePath & path)
|
||||
{
|
||||
return parseExprFromFile(path, builtins.staticEnv);
|
||||
}
|
||||
|
||||
|
||||
Expr & Evaluator::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
|
||||
Expr & Evaluator::parseExprFromFile(const CheckedSourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
|
||||
{
|
||||
auto buffer = path.readFile();
|
||||
return *parse(buffer.data(), buffer.size(), Pos::Origin(path), path.parent(), staticEnv);
|
||||
|
||||
+5
-5
@@ -393,7 +393,7 @@ private:
|
||||
/**
|
||||
* Cache used by checkSourcePath().
|
||||
*/
|
||||
std::unordered_map<Path, SourcePath> resolvedPaths;
|
||||
std::unordered_map<Path, CheckedSourcePath> resolvedPaths;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -416,12 +416,12 @@ public:
|
||||
* Check whether access to a path is allowed and throw an error if
|
||||
* not. Otherwise return the canonicalised path.
|
||||
*/
|
||||
SourcePath checkSourcePath(const SourcePath & path);
|
||||
CheckedSourcePath checkSourcePath(const SourcePath & path);
|
||||
|
||||
/**
|
||||
* If `path` refers to a directory, then append "/default.nix".
|
||||
*/
|
||||
SourcePath resolveExprPath(SourcePath path);
|
||||
CheckedSourcePath resolveExprPath(SourcePath path);
|
||||
|
||||
void checkURI(const std::string & uri);
|
||||
|
||||
@@ -536,8 +536,8 @@ public:
|
||||
/**
|
||||
* Parse a Nix expression from the specified file.
|
||||
*/
|
||||
Expr & parseExprFromFile(const SourcePath & path);
|
||||
Expr & parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
|
||||
Expr & parseExprFromFile(const CheckedSourcePath & path);
|
||||
Expr & parseExprFromFile(const CheckedSourcePath & path, std::shared_ptr<StaticEnv> & staticEnv);
|
||||
|
||||
/**
|
||||
* Parse a Nix expression from the specified string.
|
||||
|
||||
+10
-12
@@ -117,12 +117,7 @@ StringMap EvalPaths::realiseContext(const NixStringContext & context)
|
||||
return res;
|
||||
}
|
||||
|
||||
struct RealisePathFlags {
|
||||
// Whether to check that the path is allowed in pure eval mode
|
||||
bool checkForPureEval = true;
|
||||
};
|
||||
|
||||
static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
|
||||
static auto realisePath(EvalState & state, const PosIdx pos, Value & v, auto checkFn)
|
||||
{
|
||||
NixStringContext context;
|
||||
|
||||
@@ -131,17 +126,20 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, co
|
||||
try {
|
||||
StringMap rewrites = state.ctx.paths.realiseContext(context);
|
||||
|
||||
auto realPath = CanonPath(state.ctx.paths.toRealPath(rewriteStrings(path.canonical().abs(), rewrites), context));
|
||||
|
||||
return flags.checkForPureEval
|
||||
? state.ctx.paths.checkSourcePath(realPath)
|
||||
: realPath;
|
||||
return checkFn(SourcePath(CanonPath(
|
||||
state.ctx.paths.toRealPath(rewriteStrings(path.canonical().abs(), rewrites), context)
|
||||
)));
|
||||
} catch (Error & e) {
|
||||
e.addTrace(state.ctx.positions[pos], "while realising the context of path '%s'", path);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static CheckedSourcePath realisePath(EvalState & state, const PosIdx pos, Value & v)
|
||||
{
|
||||
return realisePath(state, pos, v, [&](auto p) { return state.ctx.paths.checkSourcePath(p); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Add and attribute to the given attribute map from the output name to
|
||||
* the output path, or a placeholder.
|
||||
@@ -1198,7 +1196,7 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
|
||||
can’t just catch the exception here because we still want to
|
||||
throw if something in the evaluation of `arg` tries to
|
||||
access an unauthorized path). */
|
||||
auto path = realisePath(state, pos, arg, { .checkForPureEval = false });
|
||||
auto path = realisePath(state, pos, arg, std::identity{});
|
||||
|
||||
/* SourcePath doesn't know about trailing slash. */
|
||||
auto mustBeDir = arg.type() == nString
|
||||
|
||||
@@ -21,7 +21,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||
|
||||
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
|
||||
{
|
||||
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||
if (auto path = std::get_if<CheckedSourcePath>(&pos.origin))
|
||||
xmlAttrs["path"] = path->to_string();
|
||||
xmlAttrs["line"] = fmt("%1%", pos.line);
|
||||
xmlAttrs["column"] = fmt("%1%", pos.column);
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace nix {
|
||||
|
||||
StorePath fetchToStore(
|
||||
Store & store,
|
||||
const SourcePath & path,
|
||||
const CheckedSourcePath & path,
|
||||
std::string_view name,
|
||||
FileIngestionMethod method,
|
||||
PathFilter * filter,
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace nix {
|
||||
*/
|
||||
StorePath fetchToStore(
|
||||
Store & store,
|
||||
const SourcePath & path,
|
||||
const CheckedSourcePath & path,
|
||||
std::string_view name = "source",
|
||||
FileIngestionMethod method = FileIngestionMethod::Recursive,
|
||||
PathFilter * filter = nullptr,
|
||||
|
||||
@@ -62,7 +62,7 @@ std::optional<std::string> Pos::getSource() const
|
||||
// Get rid of the null terminators added by the parser.
|
||||
return std::string(s.source->c_str());
|
||||
},
|
||||
[](const SourcePath & path) -> std::optional<std::string> {
|
||||
[](const CheckedSourcePath & path) -> std::optional<std::string> {
|
||||
try {
|
||||
return path.readFile();
|
||||
} catch (Error &) {
|
||||
|
||||
@@ -42,7 +42,7 @@ struct Pos
|
||||
auto operator<=>(const Hidden &) const = default;
|
||||
};
|
||||
|
||||
typedef std::variant<std::monostate, Stdin, String, SourcePath, Hidden> Origin;
|
||||
typedef std::variant<std::monostate, Stdin, String, CheckedSourcePath, Hidden> Origin;
|
||||
|
||||
Origin origin = std::monostate();
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ static InputAccessor::Stat convertStat(const struct stat & st)
|
||||
};
|
||||
}
|
||||
|
||||
InputAccessor::Stat SourcePath::lstat() const
|
||||
InputAccessor::Stat CheckedSourcePath::lstat() const
|
||||
{
|
||||
return convertStat(nix::lstat(path.abs()));
|
||||
}
|
||||
|
||||
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
|
||||
std::optional<InputAccessor::Stat> CheckedSourcePath::maybeLstat() const
|
||||
{
|
||||
if (auto st = nix::maybeLstat(path.abs())) {
|
||||
return convertStat(*st);
|
||||
@@ -48,12 +48,12 @@ std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
|
||||
}
|
||||
}
|
||||
|
||||
InputAccessor::Stat SourcePath::stat() const
|
||||
InputAccessor::Stat CheckedSourcePath::stat() const
|
||||
{
|
||||
return convertStat(nix::stat(path.abs()));
|
||||
}
|
||||
|
||||
std::optional<InputAccessor::Stat> SourcePath::maybeStat() const
|
||||
std::optional<InputAccessor::Stat> CheckedSourcePath::maybeStat() const
|
||||
{
|
||||
if (auto st = nix::maybeStat(path.abs())) {
|
||||
return convertStat(*st);
|
||||
@@ -62,7 +62,7 @@ std::optional<InputAccessor::Stat> SourcePath::maybeStat() const
|
||||
}
|
||||
}
|
||||
|
||||
InputAccessor::DirEntries SourcePath::readDirectory() const
|
||||
InputAccessor::DirEntries CheckedSourcePath::readDirectory() const
|
||||
{
|
||||
InputAccessor::DirEntries res;
|
||||
for (auto & entry : nix::readDirectory(path.abs())) {
|
||||
|
||||
+63
-40
@@ -14,15 +14,14 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
class CheckedSourcePath;
|
||||
|
||||
/**
|
||||
* An abstraction for accessing source files during
|
||||
* evaluation. Currently, it's just a wrapper around `CanonPath` that
|
||||
* accesses files in the regular filesystem, but in the future it will
|
||||
* support fetching files in other ways.
|
||||
* An abstraction for manipulating path names during evaluation.
|
||||
*/
|
||||
struct SourcePath
|
||||
{
|
||||
private:
|
||||
protected:
|
||||
CanonPath path;
|
||||
|
||||
public:
|
||||
@@ -38,6 +37,61 @@ public:
|
||||
*/
|
||||
SourcePath parent() const;
|
||||
|
||||
const CanonPath & canonical() const { return path; }
|
||||
|
||||
std::string to_string() const
|
||||
{ return path.abs(); }
|
||||
|
||||
/**
|
||||
* Converts this `SourcePath` into a checked `SourcePath`, consuming it.
|
||||
*/
|
||||
CheckedSourcePath unsafeIntoChecked();
|
||||
|
||||
/**
|
||||
* Append a `CanonPath` to this path.
|
||||
*/
|
||||
SourcePath operator + (const CanonPath & x) const
|
||||
{ return {path + x}; }
|
||||
|
||||
/**
|
||||
* Append a single component `c` to this path. `c` must not
|
||||
* contain a slash. A slash is implicitly added between this path
|
||||
* and `c`.
|
||||
*/
|
||||
SourcePath operator + (std::string_view c) const
|
||||
{ return {path + c}; }
|
||||
|
||||
bool operator == (const SourcePath & x) const
|
||||
{
|
||||
return path == x.path;
|
||||
}
|
||||
|
||||
bool operator != (const SourcePath & x) const
|
||||
{
|
||||
return path != x.path;
|
||||
}
|
||||
|
||||
bool operator < (const SourcePath & x) const
|
||||
{
|
||||
return path < x.path;
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const SourcePath & path);
|
||||
|
||||
/**
|
||||
* An abstraction for accessing source files during
|
||||
* evaluation. Currently, it's just a wrapper around `CanonPath` that
|
||||
* accesses files in the regular filesystem, but in the future it will
|
||||
* support fetching files in other ways.
|
||||
*/
|
||||
class CheckedSourcePath : public SourcePath
|
||||
{
|
||||
friend struct SourcePath;
|
||||
|
||||
CheckedSourcePath(CanonPath path): SourcePath(std::move(path)) {}
|
||||
|
||||
public:
|
||||
/**
|
||||
* If this `SourcePath` denotes a regular file (not a symlink),
|
||||
* return its contents; otherwise throw an error.
|
||||
@@ -96,42 +150,11 @@ public:
|
||||
Sink & sink,
|
||||
PathFilter & filter = defaultPathFilter) const
|
||||
{ sink << nix::dumpPath(path.abs(), filter); }
|
||||
|
||||
const CanonPath & canonical() const { return path; }
|
||||
|
||||
std::string to_string() const
|
||||
{ return path.abs(); }
|
||||
|
||||
/**
|
||||
* Append a `CanonPath` to this path.
|
||||
*/
|
||||
SourcePath operator + (const CanonPath & x) const
|
||||
{ return {path + x}; }
|
||||
|
||||
/**
|
||||
* Append a single component `c` to this path. `c` must not
|
||||
* contain a slash. A slash is implicitly added between this path
|
||||
* and `c`.
|
||||
*/
|
||||
SourcePath operator + (std::string_view c) const
|
||||
{ return {path + c}; }
|
||||
|
||||
bool operator == (const SourcePath & x) const
|
||||
{
|
||||
return path == x.path;
|
||||
}
|
||||
|
||||
bool operator != (const SourcePath & x) const
|
||||
{
|
||||
return path != x.path;
|
||||
}
|
||||
|
||||
bool operator < (const SourcePath & x) const
|
||||
{
|
||||
return path < x.path;
|
||||
}
|
||||
};
|
||||
|
||||
std::ostream & operator << (std::ostream & str, const SourcePath & path);
|
||||
inline CheckedSourcePath SourcePath::unsafeIntoChecked()
|
||||
{
|
||||
return CheckedSourcePath(std::move(path));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user