Cap UDT expansion depth in arg_value_name to prevent stack overflow on recursive specs - #2679
Open
Galmanus wants to merge 1 commit into
Open
Cap UDT expansion depth in arg_value_name to prevent stack overflow on recursive specs#2679Galmanus wants to merge 1 commit into
Galmanus wants to merge 1 commit into
Conversation
…n recursive specs
`Spec::arg_value_name` threads a `depth` counter but never checks it. A
contract whose spec contains a recursive type — e.g.
`struct TreeNode { children: Vec<TreeNode> }`, which the SDK and the
network accept — sent the resolver into unbounded recursion, overflowing
the stack on every `contract invoke` against the contract, including
`-- --help`. Anyone can deploy such a contract, so this crashed the CLI
of anyone who tried to interact with it.
UDTs are the only types resolved by name, so any cycle must pass through
the `ScType::Udt` arm; cap the expansion depth there and render the
type's name past the cap. Returning `None` instead would erase the
whole value name, since `None` propagates through the `?` in every
caller. `example_udts` already bounds example rendering the same way.
Complements stellar#2668, which capped XDR decode depth for deeply-nested
specs; a cycle through UDT name references never nests deeply in the
encoded XDR, so it needs this separate guard.
Fixes stellar#2445
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds safeguards and regression coverage to prevent stack overflows when rendering CLI argument value names for contracts with recursive UDTs in their spec (issue #2445).
Changes:
- Cap UDT expansion depth in
Spec::arg_value_nameto prevent unbounded recursion on recursive type graphs. - Add spec-tools regression tests for self-recursive and mutually-recursive structs, and ensure non-recursive nesting still expands fully.
- Add a soroban-cli regression test ensuring
build_custom_cmd(...).render_long_help()succeeds for a recursive spec type.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| cmd/soroban-cli/src/commands/contract/arg_parsing.rs | Adds a regression test to ensure clap help generation doesn’t crash when a contract spec includes recursive types. |
| cmd/crates/soroban-spec-tools/src/lib.rs | Adds a maximum recursion/expansion depth for UDT value-name rendering and tests for recursive and non-recursive cases. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // UDTs are the only types resolved by name, so any cycle in a | ||
| // recursive spec passes through here; cap the expansion depth | ||
| // and fall back to the type's name (#2445). | ||
| if depth > Self::MAX_UDT_VALUE_NAME_DEPTH { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #2445.
Spec::arg_value_namethreads adepthcounter but never checks it. A contract whose spec contains a recursive type (e.g.struct TreeNode { children: Vec<TreeNode> }— accepted by the SDK and the network) sends the resolver into unbounded recursionTreeNode → Vec<TreeNode> → TreeNode → …, overflowing the stack on anycontract invokeagainst the contract, including-- --help. Since anyone can deploy such a contract, this is a client-side DoS on anyone who tries to interact with it.Fix
UDTs are the only types resolved by name (
self.find(...)), so any cycle must pass through theScType::Udtarm. This caps the expansion depth there and renders the type's name past the cap — the same approachexample_udtsalready uses for examples (depth > 2→None), except returning the name keeps the outer value name intact: aNonepropagates through the?in every caller and would erase the entire help string.For the reproduction from the issue,
--helpnow renders the argument as:Tests
arg_value_name_terminates_on_self_recursive_struct— the issue'sTreeNodeshape; aborted withfatal runtime error: stack overflowbefore the fix.arg_value_name_terminates_on_mutually_recursive_structs—A ↔ Bcycle.arg_value_name_expands_non_recursive_nested_structs— output unchanged for non-recursive specs.build_custom_cmd_terminates_on_recursive_spec_type(soroban-cli) — end-to-end guard at the clap-command level, the surface the issue's repro crashes.Related
#2668 capped XDR decode depth for deeply-nested specs. A cycle through UDT name references never nests deeply in the encoded XDR, so it needs this separate guard.