Skip to content

vm: count derivation string-coercion against max-call-depth - #8

Open
dan-v wants to merge 1 commit into
psyclyx:mainfrom
dan-v:pr/coerce-depth-cap
Open

vm: count derivation string-coercion against max-call-depth#8
dan-v wants to merge 1 commit into
psyclyx:mainfrom
dan-v:pr/coerce-depth-cap

Conversation

@dan-v

@dan-v dan-v commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Problem

A self-referential list or attrset reaching a derivation argument recurses natively through the string coercion until the fiber stack faults: SIGSEGV, where Nix reports stack overflow; max-call-depth exceeded.

$ fix eval --read-write-mode -E 'let r = [ r ];
    in (derivation { name = "d"; system = "x86_64-linux"; builder = ":"; attr = r; }).drvPath'
Segmentation fault

Fix

Mirror Nix's mechanism: EvalState::coerceToString counts each coercion level against the same max-call-depth budget as function calls. The coercion here checks policy.max_call_depth (the max-call-depth setting, default 10000 as in Nix) once per level and raises the shared error. The forceThunkImpl native-stack soft guard backstops the recursion the same way it backstops deep thunk forcing, so an undersized stack degrades to a graceful error rather than a fault.

Verification

  • Boundary parity with nix-instantiate: nesting depths 5000 and 9000 evaluate to identical drvPaths on both evaluators, 11000 errors on both, and the cycle errors identically.
  • Unit tests cover deep-finite, cyclic-list, and cyclic-outPath vectors; the test binary segfaults on the parent commit.
  • Full e2e and unit suites otherwise unchanged.

A self-referential list or attrset reaching a derivation argument
(let r = [ r ]; ... attr = r) recursed natively through the coercion
until the fiber stack faulted: SIGSEGV on eval, where Nix reports
'stack overflow; max-call-depth exceeded'.

Mirror Nix's mechanism: EvalState::coerceToString counts each coercion
level against the same max-call-depth budget as function calls, so the
coercion here checks policy.max_call_depth (the max-call-depth setting,
default 10000 as in Nix) once per level and raises the shared error.
The forceThunkImpl native-stack soft guard backstops the recursion the
same way it backstops deep thunk forcing, so an undersized stack
degrades to a graceful error rather than a fault.

Nix-parity checked across the boundary: nesting depths 5000 and 9000
evaluate to identical drvPaths on both evaluators, 11000 errors on
both, and the cycle errors identically. Unit tests cover deep-finite,
cyclic list, and cyclic-outPath vectors; the test binary segfaults on
the parent commit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant