CPI Cycle Remediation
How to fix circular CPI patterns that cause infinite loops or depth limit failures.
CPI Cycle Remediation
Overview
Related Detector: CPI Cycle Detection
CPI cycle vulnerabilities arise when programs can invoke themselves or form circular call chains, exceeding Solana’s CPI depth limit of 4 and causing transaction failures. The fix is to validate CPI targets are not self-referential and to minimize CPI chain depth.
Recommended Fix
Before (Vulnerable)
pub fn proxy(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let target = &accounts[2]; // Could be self
invoke(&build_ix(target.key, data), accounts)?;
Ok(())
}
After (Fixed)
pub fn proxy(accounts: &[AccountInfo], program_id: &Pubkey, data: &[u8]) -> ProgramResult {
let target = &accounts[2];
// Prevent self-invocation
if target.key == program_id {
return Err(ProgramError::InvalidArgument);
}
invoke(&build_ix(target.key, data), accounts)?;
Ok(())
}
Alternative Mitigations
1. Whitelist allowed CPI targets
const ALLOWED_TARGETS: &[Pubkey] = &[TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID];
pub fn safe_proxy(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let target = &accounts[2];
if !ALLOWED_TARGETS.contains(target.key) {
return Err(ProgramError::IncorrectProgramId);
}
invoke(&build_ix(target.key, data), accounts)?;
Ok(())
}
2. Reentrancy guard via state flag
pub fn guarded_call(accounts: &[AccountInfo]) -> ProgramResult {
let state = &accounts[0];
let mut data = state.data.borrow_mut();
// Check and set reentrancy guard
if data[0] != 0 { return Err(ProgramError::InvalidAccountData); }
data[0] = 1; // Lock
invoke(&ix, accounts)?;
data[0] = 0; // Unlock
Ok(())
}
3. Flatten CPI chains
Refactor deep CPI chains into flat structures where the top-level program orchestrates all calls directly rather than nesting them.
Common Mistakes
Mistake 1: Only checking program name, not key
// WRONG: string comparison is not reliable
if target.key.to_string() != "MyProgram" { /* ... */ }
Always compare Pubkey values directly.
Mistake 2: Assuming Solana prevents all cycles
Solana enforces a depth limit of 4 but does not prevent cycles within that limit. Programs A and B can still call each other if the total depth stays under 4.