Merge pull request #5279 from edolstra/restrict-path-inputs
Fix relative path input handling
This commit is contained in:
commit
c81f9761cc
|
@ -89,10 +89,12 @@ static void expectType(EvalState & state, ValueType type,
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
EvalState & state, Value * value, const Pos & pos);
|
EvalState & state, Value * value, const Pos & pos,
|
||||||
|
const std::optional<Path> & baseDir);
|
||||||
|
|
||||||
static FlakeInput parseFlakeInput(EvalState & state,
|
static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
const std::string & inputName, Value * value, const Pos & pos)
|
const std::string & inputName, Value * value, const Pos & pos,
|
||||||
|
const std::optional<Path> & baseDir)
|
||||||
{
|
{
|
||||||
expectType(state, nAttrs, *value, pos);
|
expectType(state, nAttrs, *value, pos);
|
||||||
|
|
||||||
|
@ -116,7 +118,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
expectType(state, nBool, *attr.value, *attr.pos);
|
expectType(state, nBool, *attr.value, *attr.pos);
|
||||||
input.isFlake = attr.value->boolean;
|
input.isFlake = attr.value->boolean;
|
||||||
} else if (attr.name == sInputs) {
|
} else if (attr.name == sInputs) {
|
||||||
input.overrides = parseFlakeInputs(state, attr.value, *attr.pos);
|
input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir);
|
||||||
} else if (attr.name == sFollows) {
|
} else if (attr.name == sFollows) {
|
||||||
expectType(state, nString, *attr.value, *attr.pos);
|
expectType(state, nString, *attr.value, *attr.pos);
|
||||||
input.follows = parseInputPath(attr.value->string.s);
|
input.follows = parseInputPath(attr.value->string.s);
|
||||||
|
@ -154,7 +156,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
if (!attrs.empty())
|
if (!attrs.empty())
|
||||||
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos);
|
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos);
|
||||||
if (url)
|
if (url)
|
||||||
input.ref = parseFlakeRef(*url, {}, true);
|
input.ref = parseFlakeRef(*url, baseDir, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input.follows && !input.ref)
|
if (!input.follows && !input.ref)
|
||||||
|
@ -164,7 +166,8 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
EvalState & state, Value * value, const Pos & pos)
|
EvalState & state, Value * value, const Pos & pos,
|
||||||
|
const std::optional<Path> & baseDir)
|
||||||
{
|
{
|
||||||
std::map<FlakeId, FlakeInput> inputs;
|
std::map<FlakeId, FlakeInput> inputs;
|
||||||
|
|
||||||
|
@ -175,7 +178,8 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
parseFlakeInput(state,
|
parseFlakeInput(state,
|
||||||
inputAttr.name,
|
inputAttr.name,
|
||||||
inputAttr.value,
|
inputAttr.value,
|
||||||
*inputAttr.pos));
|
*inputAttr.pos,
|
||||||
|
baseDir));
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputs;
|
return inputs;
|
||||||
|
@ -191,7 +195,8 @@ static Flake getFlake(
|
||||||
state, originalRef, allowLookup, flakeCache);
|
state, originalRef, allowLookup, flakeCache);
|
||||||
|
|
||||||
// Guard against symlink attacks.
|
// Guard against symlink attacks.
|
||||||
auto flakeFile = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir + "/flake.nix");
|
auto flakeDir = canonPath(sourceInfo.actualPath + "/" + lockedRef.subdir);
|
||||||
|
auto flakeFile = canonPath(flakeDir + "/flake.nix");
|
||||||
if (!isInDir(flakeFile, sourceInfo.actualPath))
|
if (!isInDir(flakeFile, sourceInfo.actualPath))
|
||||||
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
|
throw Error("'flake.nix' file of flake '%s' escapes from '%s'",
|
||||||
lockedRef, state.store->printStorePath(sourceInfo.storePath));
|
lockedRef, state.store->printStorePath(sourceInfo.storePath));
|
||||||
|
@ -219,7 +224,7 @@ static Flake getFlake(
|
||||||
auto sInputs = state.symbols.create("inputs");
|
auto sInputs = state.symbols.create("inputs");
|
||||||
|
|
||||||
if (auto inputs = vInfo.attrs->get(sInputs))
|
if (auto inputs = vInfo.attrs->get(sInputs))
|
||||||
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos);
|
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir);
|
||||||
|
|
||||||
auto sOutputs = state.symbols.create("outputs");
|
auto sOutputs = state.symbols.create("outputs");
|
||||||
|
|
||||||
|
@ -488,10 +493,8 @@ LockedFlake lockFlake(
|
||||||
|
|
||||||
// If this input is a path, recurse it down.
|
// If this input is a path, recurse it down.
|
||||||
// This allows us to resolve path inputs relative to the current flake.
|
// This allows us to resolve path inputs relative to the current flake.
|
||||||
if (localRef.input.getType() == "path") {
|
if (localRef.input.getType() == "path")
|
||||||
localRef.input.parent = parentPath;
|
localPath = absPath(*input.ref->input.getSourcePath(), parentPath);
|
||||||
localPath = canonPath(parentPath + "/" + *input.ref->input.getSourcePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache);
|
auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache);
|
||||||
|
|
||||||
|
|
|
@ -172,8 +172,12 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
auto parsedURL = parseURL(url);
|
auto parsedURL = parseURL(url);
|
||||||
std::string fragment;
|
std::string fragment;
|
||||||
std::swap(fragment, parsedURL.fragment);
|
std::swap(fragment, parsedURL.fragment);
|
||||||
|
|
||||||
|
auto input = Input::fromURL(parsedURL);
|
||||||
|
input.parent = baseDir;
|
||||||
|
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
FlakeRef(Input::fromURL(parsedURL), get(parsedURL.query, "dir").value_or("")),
|
FlakeRef(std::move(input), get(parsedURL.query, "dir").value_or("")),
|
||||||
fragment);
|
fragment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,18 +85,26 @@ struct PathInputScheme : InputScheme
|
||||||
std::string absPath;
|
std::string absPath;
|
||||||
auto path = getStrAttr(input.attrs, "path");
|
auto path = getStrAttr(input.attrs, "path");
|
||||||
|
|
||||||
if (path[0] != '/' && input.parent) {
|
if (path[0] != '/') {
|
||||||
|
if (!input.parent)
|
||||||
|
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
|
||||||
|
|
||||||
auto parent = canonPath(*input.parent);
|
auto parent = canonPath(*input.parent);
|
||||||
|
|
||||||
// the path isn't relative, prefix it
|
// the path isn't relative, prefix it
|
||||||
absPath = canonPath(parent + "/" + path);
|
absPath = nix::absPath(path, parent);
|
||||||
|
|
||||||
// for security, ensure that if the parent is a store path, it's inside it
|
// for security, ensure that if the parent is a store path, it's inside it
|
||||||
if (!parent.rfind(store->storeDir, 0) && absPath.rfind(store->storeDir, 0))
|
if (store->isInStore(parent)) {
|
||||||
throw BadStorePath("relative path '%s' points outside of its parent's store path %s, this is a security violation", path, parent);
|
auto storePath = store->printStorePath(store->toStorePath(parent).first);
|
||||||
|
if (!isInDir(absPath, storePath))
|
||||||
|
throw BadStorePath("relative path '%s' points outside of its parent's store path '%s'", path, storePath);
|
||||||
|
}
|
||||||
} else
|
} else
|
||||||
absPath = path;
|
absPath = path;
|
||||||
|
|
||||||
|
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s'", absPath));
|
||||||
|
|
||||||
// FIXME: check whether access to 'path' is allowed.
|
// FIXME: check whether access to 'path' is allowed.
|
||||||
auto storePath = store->maybeParseStorePath(absPath);
|
auto storePath = store->maybeParseStorePath(absPath);
|
||||||
|
|
||||||
|
|
|
@ -766,7 +766,7 @@ cat > $flakeFollowsA/flake.nix <<EOF
|
||||||
{
|
{
|
||||||
description = "Flake A";
|
description = "Flake A";
|
||||||
inputs = {
|
inputs = {
|
||||||
B.url = "path:./../../flakeB";
|
B.url = "path:../flakeB";
|
||||||
};
|
};
|
||||||
outputs = { ... }: {};
|
outputs = { ... }: {};
|
||||||
}
|
}
|
||||||
|
@ -774,7 +774,7 @@ EOF
|
||||||
|
|
||||||
git -C $flakeFollowsA add flake.nix
|
git -C $flakeFollowsA add flake.nix
|
||||||
|
|
||||||
nix flake lock $flakeFollowsA 2>&1 | grep 'this is a security violation'
|
nix flake lock $flakeFollowsA 2>&1 | grep 'points outside'
|
||||||
|
|
||||||
# Test flake in store does not evaluate
|
# Test flake in store does not evaluate
|
||||||
rm -rf $badFlakeDir
|
rm -rf $badFlakeDir
|
||||||
|
|
Loading…
Reference in a new issue