Skip to content

Cap UDT expansion depth in arg_value_name to prevent stack overflow on recursive specs - #2679

Open
Galmanus wants to merge 1 commit into
stellar:mainfrom
Galmanus:fix/2445-recursive-spec-value-name
Open

Cap UDT expansion depth in arg_value_name to prevent stack overflow on recursive specs#2679
Galmanus wants to merge 1 commit into
stellar:mainfrom
Galmanus:fix/2445-recursive-spec-value-name

Conversation

@Galmanus

Copy link
Copy Markdown

Fixes #2445.

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> } — accepted by the SDK and the network) sends the resolver into unbounded recursion TreeNode → Vec<TreeNode> → TreeNode → …, overflowing the stack on any contract invoke against 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 the ScType::Udt arm. This caps the expansion depth there and renders the type's name past the cap — the same approach example_udts already uses for examples (depth > 2None), except returning the name keeps the outer value name intact: a None propagates through the ? in every caller and would erase the entire help string.

For the reproduction from the issue, --help now renders the argument as:

{ children: Array<{ children: Array<{ children: Array<TreeNode>, value: u32 }>, value: u32 }>, value: u32 }

Tests

  • arg_value_name_terminates_on_self_recursive_struct — the issue's TreeNode shape; aborted with fatal runtime error: stack overflow before the fix.
  • arg_value_name_terminates_on_mutually_recursive_structsA ↔ B cycle.
  • 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.

…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
Copilot AI balanced review requested due to automatic review settings August 14, 2026 23:01
@github-project-automation github-project-automation Bot moved this to Backlog (Not Ready) in DevX Aug 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_name to 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 {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

Stack overflow crash on contracts with recursive types in spec

2 participants