CPI Data Tampering Remediation
How to fix CPI calls with unvalidated instruction data from untrusted sources.
CPI Data Tampering Remediation
Overview
Related Detector: CPI Data Tampering
CPI data tampering vulnerabilities allow attackers to craft malicious instruction data that controls which function the called program executes. The fix is to validate instruction data structure, discriminator, and parameter bounds before CPI invocation.
Recommended Fix
Before (Vulnerable)
pub fn forward(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
let ix = Instruction { program_id: *program.key, accounts: /* ... */, data: data.to_vec() };
invoke(&ix, accounts)?; // Raw user data forwarded
Ok(())
}
After (Fixed)
const ALLOWED_DISCRIMINATOR: [u8; 8] = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
pub fn forward(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
// Validate data length
if data.len() < 8 || data.len() > MAX_IX_DATA_LEN {
return Err(ProgramError::InvalidInstructionData);
}
// Validate discriminator
if data[0..8] != ALLOWED_DISCRIMINATOR {
return Err(ProgramError::InvalidInstructionData);
}
let ix = Instruction { program_id: *program.key, accounts: /* ... */, data: data.to_vec() };
invoke(&ix, accounts)?;
Ok(())
}
Alternative Mitigations
1. Use typed instruction builders
// Build instruction from validated components, not raw bytes
let amount = u64::from_le_bytes(data[8..16].try_into()?);
if amount > MAX_AMOUNT { return Err(ProgramError::InvalidArgument); }
let ix = spl_token::instruction::transfer(token_program.key, source.key, dest.key, authority.key, &[], amount)?;
invoke(&ix, accounts)?;
2. Anchor CpiContext
// Anchor prevents raw data tampering with typed instruction builders
let cpi_ctx = CpiContext::new(program.to_account_info(), TypedAccounts { /* ... */ });
typed_instruction::handler(cpi_ctx, validated_param)?;
3. Allowlist of instruction types
const ALLOWED_INSTRUCTIONS: &[[u8; 8]] = &[TRANSFER_DISC, APPROVE_DISC];
if !ALLOWED_INSTRUCTIONS.iter().any(|d| d == &data[0..8]) {
return Err(ProgramError::InvalidInstructionData);
}
Common Mistakes
Mistake 1: Only validating length, not content
// INSUFFICIENT: length check alone does not prevent wrong instruction type
if data.len() >= 8 {
invoke(&build_ix(data), accounts)?; // Discriminator not checked
}
Mistake 2: Validating after CPI
invoke(&ix, accounts)?; // Already executed with malicious data
validate_instruction_data(&data)?; // Too late