libexpr: fix stack overflow in printAmbiguous on deeply nested structures
printAmbiguous (used by nix-instantiate --eval and nix-env) had a depth parameter, but all callers passed INT_MAX, effectively disabling the limit. The function relied on the C++ stack to eventually overflow, which could cause uncontrolled SIGSEGV crashes on deeply nested pre-forced structures. Now printAmbiguous checks depth against max-call-depth (default 10000) and throws StackOverflowError with a proper trace, consistent with other recursive value traversal functions. The function signature is updated to take EvalState& to access the settings and throw proper errors. The depth parameter now counts up from 0 instead of down from INT_MAX.
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
|
||||
namespace nix {
|
||||
|
||||
class EvalState;
|
||||
|
||||
/**
|
||||
* Print a value in the deprecated format used by `nix-instantiate --eval` and
|
||||
* `nix-env` (for manifests).
|
||||
@@ -15,7 +17,6 @@ namespace nix {
|
||||
*
|
||||
* See: https://github.com/NixOS/nix/issues/9730
|
||||
*/
|
||||
void printAmbiguous(
|
||||
Value & v, const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen, int depth);
|
||||
void printAmbiguous(EvalState & state, Value & v, std::ostream & str, std::set<const void *> * seen, size_t depth = 0);
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -2,19 +2,17 @@
|
||||
#include "nix/expr/print.hh"
|
||||
#include "nix/util/signals.hh"
|
||||
#include "nix/expr/eval.hh"
|
||||
#include "nix/expr/eval-error.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
// See: https://github.com/NixOS/nix/issues/9730
|
||||
void printAmbiguous(
|
||||
Value & v, const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen, int depth)
|
||||
void printAmbiguous(EvalState & state, Value & v, std::ostream & str, std::set<const void *> * seen, size_t depth)
|
||||
{
|
||||
checkInterrupt();
|
||||
|
||||
if (depth <= 0) {
|
||||
str << "«too deep»";
|
||||
return;
|
||||
}
|
||||
if (depth > state.settings.maxCallDepth)
|
||||
state.error<StackOverflowError>().atPos(v.determinePos(noPos)).debugThrow();
|
||||
switch (v.type()) {
|
||||
case nInt:
|
||||
str << v.integer();
|
||||
@@ -36,9 +34,9 @@ void printAmbiguous(
|
||||
str << "«repeated»";
|
||||
else {
|
||||
str << "{ ";
|
||||
for (auto & i : v.attrs()->lexicographicOrder(symbols)) {
|
||||
str << symbols[i->name] << " = ";
|
||||
printAmbiguous(*i->value, symbols, str, seen, depth - 1);
|
||||
for (auto & i : v.attrs()->lexicographicOrder(state.symbols)) {
|
||||
str << state.symbols[i->name] << " = ";
|
||||
printAmbiguous(state, *i->value, str, seen, depth + 1);
|
||||
str << "; ";
|
||||
}
|
||||
str << "}";
|
||||
@@ -54,7 +52,7 @@ void printAmbiguous(
|
||||
str << "[ ";
|
||||
for (auto v2 : v.listView()) {
|
||||
if (v2)
|
||||
printAmbiguous(*v2, symbols, str, seen, depth - 1);
|
||||
printAmbiguous(state, *v2, str, seen, depth + 1);
|
||||
else
|
||||
str << "(nullptr)";
|
||||
str << " ";
|
||||
|
||||
@@ -108,7 +108,7 @@ bool createUserEnv(
|
||||
environment. */
|
||||
auto manifestFile = ({
|
||||
std::ostringstream str;
|
||||
printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits<int>::max());
|
||||
printAmbiguous(state, manifest, str, nullptr);
|
||||
StringSource source{str.view()};
|
||||
state.store->addToStoreFromDump(
|
||||
source,
|
||||
|
||||
@@ -68,7 +68,7 @@ void processExpr(
|
||||
if (strict)
|
||||
state.forceValueDeep(vRes);
|
||||
std::set<const void *> seen;
|
||||
printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits<int>::max());
|
||||
printAmbiguous(state, vRes, std::cout, &seen);
|
||||
std::cout << std::endl;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -46,3 +46,13 @@ expectStderr 1 nix-instantiate --expr 'let x = { recurseForDerivations = true; m
|
||||
echo 'let f = n: { type = "derivation"; name = "test"; system = "x86_64-linux"; meta.nested = f (n + 1); }; in { pkg = f 0; }' > "$TEST_ROOT/deep-meta.nix"
|
||||
expectStderr 1 nix-env -qa -f "$TEST_ROOT/deep-meta.nix" --json --meta \
|
||||
| grepQuiet "stack overflow; max-call-depth exceeded"
|
||||
|
||||
# Test that nix-instantiate --eval on a pre-forced deep structure (built with
|
||||
# foldl' to avoid thunks) produces a controlled stack overflow error rather than
|
||||
# a segfault when printAmbiguous traverses the structure.
|
||||
# Note: Without the fix, this test may pass if the system stack is large enough.
|
||||
# The fix ensures we get a controlled error at max-call-depth (default 10000)
|
||||
# rather than relying on the system stack limit.
|
||||
# shellcheck disable=SC2016
|
||||
expectStderr 1 nix-instantiate --eval --expr 'builtins.foldl'\'' (acc: _: { inner = acc; }) null (builtins.genList (x: x) 20000)' \
|
||||
| grepQuiet "stack overflow; max-call-depth exceeded"
|
||||
|
||||
Reference in New Issue
Block a user