Skip to main content
Sigvex

Token Account Ownership Remediation

How to fix missing account ownership validation before CPI calls.

Remediating Token Account Ownership Issues

Overview

Related Detector: Token Account Ownership

When accounts are passed to CPI calls without verifying their owner program, attackers can substitute fake accounts with crafted data. The fix is to validate that every account used in CPI is owned by the expected program before invoking.

Before (Vulnerable)

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

pub fn process_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let source = &accounts[0]; // No owner check
    let dest = &accounts[1];   // No owner check

    solana_program::program::invoke(
        &spl_token::instruction::transfer(
            &spl_token::id(), source.key, dest.key,
            &accounts[2].key, &[], amount,
        )?,
        &[source.clone(), dest.clone(), accounts[2].clone()],
    )?;
    Ok(())
}

After (Fixed)

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

pub fn process_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let source = &accounts[0];
    let dest = &accounts[1];
    let authority = &accounts[2];

    // Validate account ownership
    if source.owner != &spl_token::id() {
        return Err(ProgramError::IllegalOwner);
    }
    if dest.owner != &spl_token::id() {
        return Err(ProgramError::IllegalOwner);
    }

    solana_program::program::invoke(
        &spl_token::instruction::transfer(
            &spl_token::id(), source.key, dest.key,
            authority.key, &[], amount,
        )?,
        &[source.clone(), dest.clone(), authority.clone()],
    )?;
    Ok(())
}

Alternative Mitigations

  1. Use Anchor typed accounts: Account<'info, TokenAccount> validates that the account is owned by the SPL Token program and deserializes it as a TokenAccount.
#[derive(Accounts)]
pub struct Transfer<'info> {
    #[account(mut)]
    pub source: Account<'info, TokenAccount>,
    #[account(mut)]
    pub dest: Account<'info, TokenAccount>,
    pub authority: Signer<'info>,
}
  1. Token-2022 compatibility: If your program supports both SPL Token and Token-2022, check ownership against both program IDs.
if source.owner != &spl_token::id() && source.owner != &spl_token_2022::id() {
    return Err(ProgramError::IllegalOwner);
}
  1. Associated Token Account derivation: Derive the expected ATA address and validate against it. ATA derivation implicitly proves ownership.

Common Mistakes

  • Checking ownership of the token program account but not token accounts: The token program identity must be validated, but so must each token account passed to it.
  • Validating ownership after CPI instead of before: The CPI may modify account state. Always validate before invoking.
  • Forgetting Token-2022: Programs that only check for the original Token program will reject valid Token-2022 accounts.
  • Assuming mint validation implies ownership: Checking that a token account’s mint field matches expectations does not validate ownership. A fake account can have any mint field.

References