Skip to content

idl-spec: avoid panics on malformed defined type generics#4246

Open
yukikm wants to merge 5 commits into
otter-sec:masterfrom
yukikm:fix-idl-defined-generics-parser-dos
Open

idl-spec: avoid panics on malformed defined type generics#4246
yukikm wants to merge 5 commits into
otter-sec:masterfrom
yukikm:fix-idl-defined-generics-parser-dos

Conversation

@yukikm
Copy link
Copy Markdown

@yukikm yukikm commented Feb 15, 2026

This PR hardens anchor-lang-idl-spec's IdlType::from_str parsing for defined types with generics.

Issue

Malformed type strings like MyStruct<Pubkey (missing closing >) would trigger a panic due to unwrap() usage in the generic parsing branch.

Fix

  • Replace unwrap() calls with structured anyhow::Error returns.
  • Add tests to ensure malformed inputs return Err (and do not panic).

Verification

cargo test -p anchor-lang-idl-spec

Write-up: https://rentry.co/anchor-idl-defined-generics-dos-2

Copilot AI review requested due to automatic review settings February 15, 2026 21:20
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 15, 2026

Someone is attempting to deploy a commit to the Solana Foundation Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the IdlType::from_str parsing logic to prevent panics when processing malformed type strings with generics and arrays. Previously, malformed inputs like MyStruct<Pubkey (missing closing >) or [u8 32] (missing semicolon) would trigger panics due to unwrap() usage in the parsing code.

Changes:

  • Replaced unwrap() calls with structured anyhow::Error returns in array and defined type generic parsing
  • Added comprehensive error messages for malformed type syntax
  • Added test cases to verify malformed inputs return errors instead of panicking

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread idl/spec/src/lib.rs Outdated
Comment on lines +389 to +392
let name = s
.get(..i)
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing name"))?
.to_owned();
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The error check here is unreachable. Since i comes from s.find('<') on line 388, it's guaranteed to be a valid index into s. The call to s.get(..i) will always return Some, making the .ok_or_else() check unnecessary. Consider using direct slicing s[..i].to_owned() or removing the error check.

Suggested change
let name = s
.get(..i)
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing name"))?
.to_owned();
let name = s[..i].to_owned();

Copilot uses AI. Check for mistakes.
Comment thread idl/spec/src/lib.rs Outdated
Comment on lines +394 to +396
let generics_str = s
.get(i + 1..)
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing generics"))?
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The error check here is unreachable. Since i comes from s.find('<') on line 388, we know that i + 1 will be at most s.len() (if '<' is at the end). The call to s.get(i + 1..) will always return Some (possibly an empty string), making the .ok_or_else() check unnecessary. Consider using direct slicing s[i + 1..] or removing the error check.

Suggested change
let generics_str = s
.get(i + 1..)
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing generics"))?
let generics_str = s[i + 1..]

Copilot uses AI. Check for mistakes.
Comment thread idl/spec/src/lib.rs
Comment on lines +408 to +412
if g.is_empty() {
return Err(anyhow!(
"Invalid defined type '{s}': empty generic argument"
));
}
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The empty generic argument check on line 408 will catch trailing commas in generic lists (e.g., "MyStruct<Pubkey,>"), which is good. However, it would also catch multiple consecutive commas (e.g., "MyStruct<Pubkey,,u64>"). Consider adding a test case for trailing commas to document this behavior.

Copilot uses AI. Check for mistakes.
Comment thread idl/spec/src/lib.rs Outdated
Comment on lines +391 to +398
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing name"))?
.to_owned();

let generics_str = s
.get(i + 1..)
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing generics"))?
.strip_suffix('>')
.ok_or_else(|| anyhow!("Invalid defined type '{s}': missing closing '>'"))?;
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

The error messages for defined types are more detailed than the existing error messages for Option and Vec types. For consistency, consider either:

  1. Making the defined type errors simpler (like "Invalid defined type"), or
  2. Improving the Option/Vec error messages to be similarly detailed (like "Invalid Option type '{s}': missing closing '>'")

This would make error messages more consistent across the codebase.

Copilot uses AI. Check for mistakes.
Comment thread idl/spec/src/lib.rs
fn malformed_defined_empty_generics_returns_err() {
// Previously this would panic due to `.strip_suffix('>').unwrap()` (and/or parse weirdly).
assert!(IdlType::from_str("MyStruct<>").is_err());
}
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

Consider adding a test case for nested generic types with malformed syntax, such as "MyStruct<Option" (missing two closing angle brackets). This would verify that the recursive parsing in generic arguments properly propagates errors.

Suggested change
}
}
#[test]
fn malformed_defined_nested_missing_closing_angles_returns_err() {
// Ensure recursive parsing of generic arguments propagates syntax errors from nested generics.
assert!(IdlType::from_str("MyStruct<Option<Pubkey>").is_err());
}

Copilot uses AI. Check for mistakes.
Comment thread idl/spec/src/lib.rs
Comment on lines +503 to +514
#[test]
fn malformed_array_missing_semicolon_returns_err() {
// Previously this would panic due to `.rsplit_once(';').unwrap()`.
assert!(IdlType::from_str("[u8 32]").is_err());
}

#[test]
fn malformed_array_invalid_element_type_returns_err() {
// Previously this would panic due to `IdlType::from_str(raw_type).unwrap()`.
// Use a type that is *syntactically* invalid (missing '>') so `from_str` returns Err.
assert!(IdlType::from_str("[Option<Pubkey; 32]").is_err());
}
Copy link

Copilot AI Feb 15, 2026

Choose a reason for hiding this comment

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

Consider adding a test case for malformed multidimensional arrays, such as "[[u8; 16]; 32" (missing closing ']'). This would verify that errors are properly propagated through the recursive array_from_str calls.

Copilot uses AI. Check for mistakes.
@jamie-osec
Copy link
Copy Markdown
Collaborator

This appears to conflict with #4247, with both rewriting array_from_str. Please fold them into one PR

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.

3 participants