Duplicate Mutable Accounts Remediation
How to prevent duplicate mutable account aliasing in Solana programs by adding key equality constraints and explicit address comparisons.
Duplicate Mutable Accounts Remediation
Overview
Duplicate mutable account vulnerabilities occur when a caller passes the same public key at two positions in an instruction’s accounts array where both are declared mutable. The remediation is to add an Anchor constraint asserting the two account keys are different, or to add an explicit key comparison at the start of native program handlers.
Related Detector: Duplicate Mutable Accounts
Recommended Fix
Before (Vulnerable)
use anchor_lang::prelude::*;
use anchor_spl::token::{TokenAccount};
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub source: Account<'info, TokenAccount>,
#[account(mut)] // Caller can pass the same account as source
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
}
After (Fixed)
use anchor_lang::prelude::*;
use anchor_spl::token::{TokenAccount};
#[error_code]
pub enum MyError {
#[msg("Source and destination accounts must be different")]
SameAccount,
}
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut)]
pub source: Account<'info, TokenAccount>,
#[account(
mut,
constraint = destination.key() != source.key() @ MyError::SameAccount
)]
pub destination: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
}
Alternative Mitigations
Native program: explicit key check at handler entry:
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError};
pub fn process_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let source = &accounts[0];
let destination = &accounts[1];
// Reject before any mutable borrows
if source.key == destination.key {
return Err(ProgramError::InvalidAccountData);
}
**source.lamports.borrow_mut() -= amount;
**destination.lamports.borrow_mut() += amount;
Ok(())
}
Three-account patterns — for swap instructions with A→B and B→A flows, also check that all three accounts (user, pool_a, pool_b) are mutually distinct:
#[derive(Accounts)]
pub struct Swap<'info> {
#[account(mut)]
pub user_token_a: Account<'info, TokenAccount>,
#[account(mut, constraint = pool_token_a.key() != user_token_a.key())]
pub pool_token_a: Account<'info, TokenAccount>,
#[account(
mut,
constraint = pool_token_b.key() != user_token_a.key(),
constraint = pool_token_b.key() != pool_token_a.key()
)]
pub pool_token_b: Account<'info, TokenAccount>,
}
Common Mistakes
Relying on the SPL Token program to check — the SPL Token program validates source ≠ destination for its own instructions, but any business logic that processes accounts before the CPI can still be vulnerable.
Checking only the first pair — for instructions with three or more mutable accounts, all pairs must be validated, not just the most obvious ones.