Skip to main content
Sigvex

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 account-validation framework 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

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.

References