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.
Recommended Fix
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
- 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>,
}
- 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);
}
- 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.