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:
eldritch horrors
2025-01-10 15:20:27 -08:00
committed by Jade Lovelace
parent 8db8ac9a67
commit c948b350fb
12 changed files with 107 additions and 83 deletions
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
cant 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
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -6,7 +6,7 @@ namespace nix {
StorePath fetchToStore(
Store & store,
const SourcePath & path,
const CheckedSourcePath & path,
std::string_view name,
FileIngestionMethod method,
PathFilter * filter,
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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 &) {
+1 -1
View File
@@ -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();
+5 -5
View File
@@ -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
View File
@@ -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));
}
}