Skip to main content
Sigvex

CPI Data Tampering

Detects CPI calls with potentially tampered instruction data from untrusted sources.

CPI Data Tampering

Overview

Remediation Guide: How to Fix CPI Data Tampering

The CPI data tampering detector identifies cross-program invocations where instruction data or account lists come from untrusted sources (user input, account data, memory loads) without validation. Attackers can craft malicious instruction data to call unintended program functions, bypass instruction-level access controls, or manipulate parameters to privileged operations.

Why This Is an Issue

CPI instruction data determines which function the called program executes and with what parameters. If this data comes from user-controlled input without validation, an attacker can change the instruction discriminator to call admin functions, alter amounts in transfer operations, or modify any parameter the called program processes. This effectively gives attackers arbitrary control over the called program’s execution.

CWE mapping: CWE-20 (Improper Input Validation).

How to Resolve

Native Solana

pub fn safe_cpi(accounts: &[AccountInfo], user_amount: u64) -> ProgramResult {
    // Build instruction data from validated components, not raw user input
    let ix_data = spl_token::instruction::TokenInstruction::Transfer { amount: user_amount }.pack();

    // Validate amount bounds
    if user_amount == 0 || user_amount > MAX_TRANSFER {
        return Err(ProgramError::InvalidArgument);
    }

    let ix = Instruction { program_id: spl_token::id(), accounts: /* ... */, data: ix_data };
    invoke(&ix, accounts)?;
    Ok(())
}

Anchor

// Anchor CpiContext builds typed instruction data, preventing raw tampering
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
    require!(amount > 0 && amount <= MAX_TRANSFER, ErrorCode::InvalidAmount);

    let cpi_ctx = CpiContext::new(
        ctx.accounts.token_program.to_account_info(),
        token::Transfer { /* typed accounts */ },
    );
    token::transfer(cpi_ctx, amount)?;
    Ok(())
}

Examples

Vulnerable Code

pub fn proxy_call(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    // Raw user data passed directly to CPI -- attacker controls instruction type
    let ix = Instruction {
        program_id: *accounts[3].key,
        accounts: /* ... */,
        data: data.to_vec(), // VULNERABLE: raw user input as instruction data
    };
    invoke(&ix, accounts)?;
    Ok(())
}

Fixed Code

pub fn proxy_call(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
    // Validate instruction discriminator
    if data.len() < 8 { return Err(ProgramError::InvalidInstructionData); }
    let discriminator = &data[0..8];

    if discriminator != ALLOWED_INSTRUCTION_DISCRIMINATOR {
        return Err(ProgramError::InvalidInstructionData);
    }

    let ix = Instruction { program_id: *accounts[3].key, accounts: /* ... */, data: data.to_vec() };
    invoke(&ix, accounts)?;
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "cpi-data-tampering",
  "severity": "high",
  "confidence": 0.55,
  "description": "CPI call uses instruction data from an untrusted source without validation. Attackers can craft malicious instruction data to call unintended functions.",
  "location": { "function": "proxy_call", "block": 0, "stmt": 1 }
}

Detection Methodology

  1. Taint analysis: Identifies instruction data and account list expressions that originate from parameters, memory loads, or variables (untrusted sources).
  2. Validation detection: Checks for existing data validation patterns including discriminator comparisons and bounds checks on loaded data.
  3. Dynamic construction detection: Flags CPI calls with instruction data built via binary operations or memory manipulation.
  4. Context adjustment: Confidence is significantly reduced for Anchor programs, which use typed instruction builders.

Limitations

  • The detector conservatively treats all variable-sourced data as untrusted, which may flag validated data that flows through complex paths.
  • Instruction builders from external crates that construct safe data are not recognized.
  • Validation performed in separate functions is not tracked across call boundaries.

References