CPI Signer Propagation
Detects unsafe signer propagation in nested CPI calls without re-validation.
CPI Signer Propagation
Overview
Remediation Guide: How to Fix CPI Signer Propagation
The CPI signer propagation detector identifies cases where signing authority is automatically inherited through nested CPI calls without explicit re-validation at each hop. In Solana, when a program invokes another program using invoke_signed with PDA seeds, the called program inherits signing authority. In complex CPI chains, this automatic propagation can lead to privilege escalation if signers are not re-validated between invocations.
The detector flags three escalating patterns:
- Sequential CPIs without signer re-validation between a PDA-signed CPI and a subsequent CPI (High).
- State modification after PDA-signed CPI without re-checking signer status (High).
- Deep PDA-signed CPI chains with three or more signed CPIs, compounding authorization risk (Critical).
Why This Is an Issue
When a program makes a PDA-signed CPI, the called program can act with the PDA’s signing authority. If the calling program then makes a second CPI or modifies state without re-validating the signer, the intermediate program may have changed the authorization context. An attacker can exploit this gap to perform unauthorized operations in downstream programs or corrupt local state based on stale signer assumptions.
CWE mapping: CWE-863 (Incorrect Authorization), CWE-367 (TOCTOU Race Condition), CWE-250 (Execution with Unnecessary Privileges).
How to Resolve
Native Solana
pub fn multi_step(accounts: &[AccountInfo]) -> ProgramResult {
let authority = &accounts[0];
// First CPI with PDA signing
invoke_signed(&first_ix, accounts, &[&seeds])?;
// Re-validate signer before next operation
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Second CPI is now safe
invoke(&second_ix, accounts)?;
Ok(())
}
Anchor
pub fn multi_step(ctx: Context<MultiStep>) -> Result<()> {
// First CPI
let cpi_ctx = CpiContext::new_with_signer(/* ... */);
token::transfer(cpi_ctx, amount)?;
// Re-validate signer after CPI
require!(ctx.accounts.authority.is_signer, ErrorCode::MissingSigner);
// Safe to proceed with second operation
// ...
Ok(())
}
Examples
Vulnerable Code
pub fn compound_transfer(accounts: &[AccountInfo]) -> ProgramResult {
// PDA-signed CPI to token program
invoke_signed(&transfer_ix, accounts, &[&pda_seeds])?;
// State modification without re-checking signer -- VULNERABLE
let vault = &accounts[2];
let mut data = vault.data.borrow_mut();
data[0..8].copy_from_slice(&new_balance.to_le_bytes());
Ok(())
}
Fixed Code
pub fn compound_transfer(accounts: &[AccountInfo]) -> ProgramResult {
invoke_signed(&transfer_ix, accounts, &[&pda_seeds])?;
// Re-validate authority after CPI
let authority = &accounts[0];
if !authority.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let vault = &accounts[2];
let mut data = vault.data.borrow_mut();
data[0..8].copy_from_slice(&new_balance.to_le_bytes());
Ok(())
}
Sample Sigvex Output
{
"detector_id": "cpi-signer-propagation",
"severity": "high",
"confidence": 0.80,
"description": "PDA-signed CPI at block 0 stmt 0 is followed by another CPI without signer re-validation.",
"location": { "function": "compound_transfer", "block": 0, "stmt": 2 }
}
Detection Methodology
- CPI collection: Scans for all
InvokeCpistatements, noting which have PDA seeds. - Signer check tracking: Records
CheckSignerstatements and their positions. - Gap analysis: For sequential CPI pairs where the first has seeds, checks if a signer validation exists between them.
- State mutation tracking: Detects
StoreAccountData,TransferLamports, andStoreoperations after PDA-signed CPIs without intervening signer checks. - Depth analysis: Flags functions with three or more PDA-signed CPIs as deep chains with compounded risk.
Limitations
- Signer validation performed inside called functions is not visible at the caller level.
- The detector cannot track signer status changes across multiple blocks with complex control flow.
- Functions that use separate PDA authorities for different operations may be flagged if the structure is not visible in the HIR.
Related Detectors
- CPI Signer Simulation — detects CPI signer simulation attacks
- CPI Reentrancy — detects re-entrant CPI patterns