Critical Program ID Validation
Detects CPI calls to critical system programs without explicit address validation.
Critical Program ID Validation
Overview
Remediation Guide: How to Fix Critical Program ID Validation
The critical program ID validation detector identifies CPI calls to well-known system programs (System Program, SPL Token, Token-2022, Metaplex) without explicit address validation against known constants. These programs have fixed, published addresses that should always be hardcoded and verified before invocation. If an attacker can substitute a malicious program in place of a critical system program, they can intercept token transfers, steal funds, or corrupt on-chain state.
Why This Is an Issue
Critical system programs such as the System Program, SPL Token Program, and Token-2022 Program have fixed, well-known addresses. When a program makes a CPI to one of these targets using a variable program ID without first checking it against the expected constant, an attacker can supply a malicious program that masquerades as the system program. The malicious program receives the full account context and any PDA signing authority, enabling it to drain funds or manipulate state.
CWE mapping: CWE-20 (Improper Input Validation).
How to Resolve
Native Solana
use solana_program::pubkey::Pubkey;
const SYSTEM_PROGRAM_ID: Pubkey = solana_program::system_program::id();
pub fn process(accounts: &[AccountInfo]) -> ProgramResult {
let system_program = &accounts[3];
// Validate the program ID is the real System Program
if system_program.key != &SYSTEM_PROGRAM_ID {
return Err(ProgramError::IncorrectProgramId);
}
// Safe to invoke
invoke(&create_account_ix, accounts)?;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct Initialize<'info> {
// Program<'info, System> validates the program ID at deserialization
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
}
Examples
Vulnerable Code
pub fn transfer_sol(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let system_program = &accounts[2]; // Not validated!
let ix = solana_program::system_instruction::transfer(
accounts[0].key,
accounts[1].key,
amount,
);
invoke(&ix, accounts)?; // Attacker can redirect to malicious program
Ok(())
}
Fixed Code
pub fn transfer_sol(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let system_program = &accounts[2];
if system_program.key != &solana_program::system_program::id() {
return Err(ProgramError::IncorrectProgramId);
}
let ix = solana_program::system_instruction::transfer(
accounts[0].key,
accounts[1].key,
amount,
);
invoke(&ix, accounts)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "critical-program-id-validation",
"severity": "critical",
"confidence": 0.70,
"description": "CPI is made to program v1 without validating the program ID against a known constant. If this is a critical system program, an attacker could substitute a malicious program to steal funds or corrupt state.",
"location": { "function": "transfer_sol", "block": 0, "stmt": 1 }
}
Detection Methodology
The detector performs a two-pass analysis over the function’s HIR representation:
- Validation collection: Scans for
CheckKeystatements that compare account variables against constant values, building a set of validated program IDs. - CPI analysis: Identifies all
InvokeCpioperations and checks whether the program target is a hardcoded constant or a validated variable. Unvalidated variable-sourced CPI targets are flagged. - Context adjustment: Confidence is reduced for Anchor programs (where
Program<'info, T>validates automatically), admin functions, and read-only functions.
Limitations
- The detector conservatively flags all unvalidated CPIs since it cannot determine the actual target program at static analysis time.
- Validation performed in called functions or via indirect lookups is not tracked.
- Programs that use an on-chain registry of trusted program IDs may produce false positives.
Related Detectors
- Arbitrary CPI — detects unvalidated CPI targets in general
- CPI Program Validation — broader program validation checks