mirror of
https://github.com/NixOS/nix
synced 2025-06-25 10:41:16 +02:00
Add a special type of context for the result of toString
When you apply `builtins.toString` to a path value representing a path in the Nix store (as is the case with flake inputs), historically you got a string without context (e.g. `/nix/store/...-source`). This is broken, since it allows you to pass a store path to a derivation/toFile without a proper store reference. This is especially a problem with lazy trees, since the store path is a virtual path that doesn't exist and can be different every time. For backwards compatibility, and to warn users about this unsafe use of `toString`, we now keep track of such strings as a special type of context.
This commit is contained in:
parent
8c568277fd
commit
2a35d8f800
10 changed files with 129 additions and 10 deletions
|
@ -23,6 +23,11 @@ struct Arbitrary<NixStringContextElem::DrvDeep> {
|
||||||
static Gen<NixStringContextElem::DrvDeep> arbitrary();
|
static Gen<NixStringContextElem::DrvDeep> arbitrary();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct Arbitrary<NixStringContextElem::Path> {
|
||||||
|
static Gen<NixStringContextElem::Path> arbitrary();
|
||||||
|
};
|
||||||
|
|
||||||
template<>
|
template<>
|
||||||
struct Arbitrary<NixStringContextElem> {
|
struct Arbitrary<NixStringContextElem> {
|
||||||
static Gen<NixStringContextElem> arbitrary();
|
static Gen<NixStringContextElem> arbitrary();
|
||||||
|
|
|
@ -15,6 +15,15 @@ Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arb
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Gen<NixStringContextElem::Path> Arbitrary<NixStringContextElem::Path>::arbitrary()
|
||||||
|
{
|
||||||
|
return gen::map(gen::arbitrary<StorePath>(), [](StorePath storePath) {
|
||||||
|
return NixStringContextElem::Path{
|
||||||
|
.storePath = storePath,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
|
Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
|
||||||
{
|
{
|
||||||
return gen::mapcat(
|
return gen::mapcat(
|
||||||
|
@ -30,6 +39,9 @@ Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
|
||||||
case 2:
|
case 2:
|
||||||
return gen::map(
|
return gen::map(
|
||||||
gen::arbitrary<NixStringContextElem::Built>(), [](NixStringContextElem a) { return a; });
|
gen::arbitrary<NixStringContextElem::Built>(), [](NixStringContextElem a) { return a; });
|
||||||
|
case 3:
|
||||||
|
return gen::map(
|
||||||
|
gen::arbitrary<NixStringContextElem::Path>(), [](NixStringContextElem a) { return a; });
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -628,6 +628,9 @@ string_t AttrCursor::getStringWithContext()
|
||||||
[&](const NixStringContextElem::Opaque & o) -> const StorePath & {
|
[&](const NixStringContextElem::Opaque & o) -> const StorePath & {
|
||||||
return o.path;
|
return o.path;
|
||||||
},
|
},
|
||||||
|
[&](const NixStringContextElem::Path & p) -> const StorePath & {
|
||||||
|
abort(); // FIXME
|
||||||
|
},
|
||||||
}, c.raw);
|
}, c.raw);
|
||||||
if (!root->state.store->isValidPath(path)) {
|
if (!root->state.store->isValidPath(path)) {
|
||||||
valid = false;
|
valid = false;
|
||||||
|
|
|
@ -952,8 +952,8 @@ void EvalState::mkPos(Value & v, PosIdx p)
|
||||||
// FIXME: only do this for virtual store paths?
|
// FIXME: only do this for virtual store paths?
|
||||||
attrs.alloc(sFile).mkString(path->path.abs(),
|
attrs.alloc(sFile).mkString(path->path.abs(),
|
||||||
{
|
{
|
||||||
NixStringContextElem::Opaque{
|
NixStringContextElem::Path{
|
||||||
.path = store->toStorePath(path->path.abs()).first
|
.storePath = store->toStorePath(path->path.abs()).first
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
else
|
else
|
||||||
|
@ -2277,7 +2277,10 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos, errorCtx);
|
auto s = forceString(v, pos, errorCtx);
|
||||||
if (v.context()) {
|
if (v.context()) {
|
||||||
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow();
|
NixStringContext context;
|
||||||
|
copyContext(v, context);
|
||||||
|
if (hasContext(context))
|
||||||
|
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow();
|
||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -2336,7 +2339,16 @@ BackedStringView EvalState::coerceToString(
|
||||||
v.payload.path.path
|
v.payload.path.path
|
||||||
: copyToStore
|
: copyToStore
|
||||||
? store->printStorePath(copyPathToStore(context, v.path()))
|
? store->printStorePath(copyPathToStore(context, v.path()))
|
||||||
: std::string(v.path().path.abs());
|
: ({
|
||||||
|
auto path = v.path();
|
||||||
|
if (path.accessor == rootFS && store->isInStore(path.path.abs())) {
|
||||||
|
context.insert(
|
||||||
|
NixStringContextElem::Path{
|
||||||
|
.storePath = store->toStorePath(path.path.abs()).first
|
||||||
|
});
|
||||||
|
}
|
||||||
|
std::string(path.path.abs());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.type() == nAttrs) {
|
if (v.type() == nAttrs) {
|
||||||
|
@ -2499,6 +2511,9 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
|
||||||
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
|
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
|
||||||
return std::move(b);
|
return std::move(b);
|
||||||
},
|
},
|
||||||
|
[&](NixStringContextElem::Path && p) -> SingleDerivedPath {
|
||||||
|
abort(); // FIXME
|
||||||
|
},
|
||||||
}, ((NixStringContextElem &&) *context.begin()).raw);
|
}, ((NixStringContextElem &&) *context.begin()).raw);
|
||||||
return {
|
return {
|
||||||
std::move(derivedPath),
|
std::move(derivedPath),
|
||||||
|
|
|
@ -54,10 +54,35 @@ struct NixStringContextElem {
|
||||||
*/
|
*/
|
||||||
using Built = SingleDerivedPath::Built;
|
using Built = SingleDerivedPath::Built;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store path that will not result in a store reference when
|
||||||
|
* used in a derivation or toFile.
|
||||||
|
*
|
||||||
|
* When you apply `builtins.toString` to a path value representing
|
||||||
|
* a path in the Nix store (as is the case with flake inputs),
|
||||||
|
* historically you got a string without context
|
||||||
|
* (e.g. `/nix/store/...-source`). This is broken, since it allows
|
||||||
|
* you to pass a store path to a derivation/toFile without a
|
||||||
|
* proper store reference. This is especially a problem with lazy
|
||||||
|
* trees, since the store path is a virtual path that doesn't
|
||||||
|
* exist.
|
||||||
|
*
|
||||||
|
* For backwards compatibility, and to warn users about this
|
||||||
|
* unsafe use of `toString`, we keep track of such strings as a
|
||||||
|
* special type of context.
|
||||||
|
*/
|
||||||
|
struct Path
|
||||||
|
{
|
||||||
|
StorePath storePath;
|
||||||
|
|
||||||
|
GENERATE_CMP(Path, me->storePath);
|
||||||
|
};
|
||||||
|
|
||||||
using Raw = std::variant<
|
using Raw = std::variant<
|
||||||
Opaque,
|
Opaque,
|
||||||
DrvDeep,
|
DrvDeep,
|
||||||
Built
|
Built,
|
||||||
|
Path
|
||||||
>;
|
>;
|
||||||
|
|
||||||
Raw raw;
|
Raw raw;
|
||||||
|
@ -82,4 +107,10 @@ struct NixStringContextElem {
|
||||||
|
|
||||||
typedef std::set<NixStringContextElem> NixStringContext;
|
typedef std::set<NixStringContextElem> NixStringContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if `context` has no elements other than
|
||||||
|
* `NixStringContextElem::Path`.
|
||||||
|
*/
|
||||||
|
bool hasContext(const NixStringContext & context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,9 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
|
||||||
if (maybePathsOut)
|
if (maybePathsOut)
|
||||||
maybePathsOut->emplace(d.drvPath);
|
maybePathsOut->emplace(d.drvPath);
|
||||||
},
|
},
|
||||||
|
[&](const NixStringContextElem::Path & p) {
|
||||||
|
// FIXME
|
||||||
|
},
|
||||||
}, c.raw);
|
}, c.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1438,6 +1441,9 @@ static void derivationStrictInternal(
|
||||||
[&](const NixStringContextElem::Opaque & o) {
|
[&](const NixStringContextElem::Opaque & o) {
|
||||||
drv.inputSrcs.insert(state.devirtualize(o.path, &rewrites));
|
drv.inputSrcs.insert(state.devirtualize(o.path, &rewrites));
|
||||||
},
|
},
|
||||||
|
[&](const NixStringContextElem::Path & p) {
|
||||||
|
// FIXME: do something
|
||||||
|
},
|
||||||
}, c.raw);
|
}, c.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2346,10 +2352,21 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
|
||||||
std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
|
std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
|
||||||
|
|
||||||
StorePathSet refs;
|
StorePathSet refs;
|
||||||
|
StringMap rewrites;
|
||||||
|
|
||||||
for (auto c : context) {
|
for (auto c : context) {
|
||||||
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
|
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
|
||||||
refs.insert(p->path);
|
refs.insert(p->path);
|
||||||
|
else if (auto p = std::get_if<NixStringContextElem::Path>(&c.raw)) {
|
||||||
|
if (contents.find(p->storePath.to_string()) != contents.npos) {
|
||||||
|
warn(
|
||||||
|
"Using 'builtins.toFile' to create a file named '%s' that references the store path '%s' without a proper context. "
|
||||||
|
"The resulting file will not have a correct store reference, so this is unreliable and may stop working in the future.",
|
||||||
|
name,
|
||||||
|
state.store->printStorePath(p->storePath));
|
||||||
|
state.devirtualize(p->storePath, &rewrites);
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
state.error<EvalError>(
|
state.error<EvalError>(
|
||||||
"files created by %1% may not reference derivations, but %2% references %3%",
|
"files created by %1% may not reference derivations, but %2% references %3%",
|
||||||
|
@ -2359,6 +2376,8 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
|
||||||
).atPos(pos).debugThrow();
|
).atPos(pos).debugThrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contents = rewriteStrings(contents, rewrites);
|
||||||
|
|
||||||
auto storePath = settings.readOnlyMode
|
auto storePath = settings.readOnlyMode
|
||||||
? state.store->makeFixedOutputPathFromCA(name, TextInfo {
|
? state.store->makeFixedOutputPathFromCA(name, TextInfo {
|
||||||
.hash = hashString(HashAlgorithm::SHA256, contents),
|
.hash = hashString(HashAlgorithm::SHA256, contents),
|
||||||
|
|
|
@ -7,9 +7,15 @@ namespace nix {
|
||||||
|
|
||||||
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
NixStringContext context, filtered;
|
||||||
|
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
||||||
v.mkString(*s);
|
|
||||||
|
for (auto & c : context)
|
||||||
|
if (auto * p = std::get_if<NixStringContextElem::Path>(&c.raw))
|
||||||
|
filtered.insert(*p);
|
||||||
|
|
||||||
|
v.mkString(*s, filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_unsafeDiscardStringContext({
|
static RegisterPrimOp primop_unsafeDiscardStringContext({
|
||||||
|
@ -21,12 +27,19 @@ static RegisterPrimOp primop_unsafeDiscardStringContext({
|
||||||
.fun = prim_unsafeDiscardStringContext,
|
.fun = prim_unsafeDiscardStringContext,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bool hasContext(const NixStringContext & context)
|
||||||
|
{
|
||||||
|
for (auto & c : context)
|
||||||
|
if (!std::get_if<NixStringContextElem::Path>(&c.raw))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
||||||
v.mkBool(!context.empty());
|
v.mkBool(hasContext(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_hasContext({
|
static RegisterPrimOp primop_hasContext({
|
||||||
|
@ -103,7 +116,7 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
|
||||||
|
|
||||||
auto contextSize = context.size();
|
auto contextSize = context.size();
|
||||||
if (contextSize != 1) {
|
if (contextSize != 1) {
|
||||||
state.error<EvalError>(
|
state.error<EvalError>(
|
||||||
"context of string '%s' must have exactly one element, but has %d",
|
"context of string '%s' must have exactly one element, but has %d",
|
||||||
|
@ -136,6 +149,10 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
|
||||||
above does not make much sense. */
|
above does not make much sense. */
|
||||||
return std::move(c);
|
return std::move(c);
|
||||||
},
|
},
|
||||||
|
[&](const NixStringContextElem::Path & p) -> NixStringContextElem::DrvDeep {
|
||||||
|
// FIXME: don't know what to do here.
|
||||||
|
abort();
|
||||||
|
},
|
||||||
}, context.begin()->raw) }),
|
}, context.begin()->raw) }),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -206,6 +223,8 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
[&](NixStringContextElem::Opaque && o) {
|
[&](NixStringContextElem::Opaque && o) {
|
||||||
contextInfos[std::move(o.path)].path = true;
|
contextInfos[std::move(o.path)].path = true;
|
||||||
},
|
},
|
||||||
|
[&](NixStringContextElem::Path && p) {
|
||||||
|
},
|
||||||
}, ((NixStringContextElem &&) i).raw);
|
}, ((NixStringContextElem &&) i).raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
json printValueAsJSON(EvalState & state, bool strict,
|
json printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
|
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
|
||||||
{
|
{
|
||||||
|
@ -33,6 +34,8 @@ json printValueAsJSON(EvalState & state, bool strict,
|
||||||
copyContext(v, context);
|
copyContext(v, context);
|
||||||
// FIXME: only use the context from `v`.
|
// FIXME: only use the context from `v`.
|
||||||
// FIXME: make devirtualization configurable?
|
// FIXME: make devirtualization configurable?
|
||||||
|
// FIXME: don't devirtualize here? It's redundant if
|
||||||
|
// 'toFile' or 'derivation' also do it.
|
||||||
out = state.devirtualize(v.c_str(), context);
|
out = state.devirtualize(v.c_str(), context);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,11 @@ NixStringContextElem NixStringContextElem::parse(
|
||||||
.drvPath = StorePath { s.substr(1) },
|
.drvPath = StorePath { s.substr(1) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case '@': {
|
||||||
|
return NixStringContextElem::Path {
|
||||||
|
.storePath = StorePath { s.substr(1) },
|
||||||
|
};
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
// Ensure no '!'
|
// Ensure no '!'
|
||||||
if (s.find("!") != std::string_view::npos) {
|
if (s.find("!") != std::string_view::npos) {
|
||||||
|
@ -100,6 +105,10 @@ std::string NixStringContextElem::to_string() const
|
||||||
res += '=';
|
res += '=';
|
||||||
res += d.drvPath.to_string();
|
res += d.drvPath.to_string();
|
||||||
},
|
},
|
||||||
|
[&](const NixStringContextElem::Path & p) {
|
||||||
|
res += '@';
|
||||||
|
res += p.storePath.to_string();
|
||||||
|
},
|
||||||
}, raw);
|
}, raw);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
|
@ -92,6 +92,9 @@ UnresolvedApp InstallableValue::toApp(EvalState & state)
|
||||||
.path = o.path,
|
.path = o.path,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
[&](const NixStringContextElem::Path & p) -> DerivedPath {
|
||||||
|
abort(); // FIXME
|
||||||
|
},
|
||||||
}, c.raw));
|
}, c.raw));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue