Cross-Account Relationship Remediation
How to fix missing relationship validation between related accounts.
Cross-Account Relationship Remediation
Overview
Related Detector: Cross-Account Relationship Validation
Cross-account relationship vulnerabilities occur when accounts that should be related (token account to mint, ATA to owner) are used together without verifying the relationship. The fix is to validate all account relationships before performing operations.
Recommended Fix
Before (Vulnerable)
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let vault_token = &accounts[0]; // No relationship check
let user_token = &accounts[1];
let authority = &accounts[2];
invoke(&transfer_ix, accounts)?;
Ok(())
}
After (Fixed)
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let vault_token = &accounts[0];
let user_token = &accounts[1];
let authority = &accounts[2];
// Validate vault ownership
let vault_data = spl_token::state::Account::unpack(&vault_token.data.borrow())?;
if vault_data.owner != *authority.key {
return Err(ProgramError::InvalidAccountData);
}
// Validate both accounts use same mint
let user_data = spl_token::state::Account::unpack(&user_token.data.borrow())?;
if vault_data.mint != user_data.mint {
return Err(ProgramError::InvalidAccountData);
}
invoke(&transfer_ix, accounts)?;
Ok(())
}
Alternative Mitigations
1. Anchor has_one constraint
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(mut, has_one = mint, has_one = authority)]
pub vault: Account<'info, TokenAccount>,
#[account(mut, constraint = user_token.mint == vault.mint)]
pub user_token: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub mint: Account<'info, Mint>,
}
2. ATA derivation verification
let expected_ata = get_associated_token_address(owner.key, mint.key);
if user_token.key != &expected_ata {
return Err(ProgramError::InvalidAccountData);
}
Common Mistakes
Mistake 1: Validating individual accounts but not their relationships
// INSUFFICIENT: both accounts are valid individually but may not be related
if !authority.is_signer { return Err(...); }
if vault.owner == &spl_token::id() { /* OK */ }
// Missing: does vault.owner match authority?
Mistake 2: Only checking one direction
// INCOMPLETE: checks source -> authority but not destination -> mint
let data = Account::unpack(&source.data.borrow())?;
if data.owner != *authority.key { return Err(...); }
// Missing: data.mint == expected_mint