Skip to main content
Sigvex

Shared Mutable Race Remediation

How to fix race conditions from shared mutable account access.

Remediating Shared Mutable Race Conditions

Overview

Related Detector: Shared Mutable Race

Shared mutable race conditions occur when the same account is written through multiple code paths (direct writes and CPI) without verifying that accounts in different positions are actually distinct. The fix is to add explicit duplicate account checks before any mutable operations.

Before (Vulnerable)

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

pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let account_a = &accounts[0];
    let account_b = &accounts[1];

    // No duplicate check -- if account_a == account_b, state corruption
    **account_a.lamports.borrow_mut() -= amount;
    **account_b.lamports.borrow_mut() += amount;
    Ok(())
}

After (Fixed)

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

pub fn swap(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let account_a = &accounts[0];
    let account_b = &accounts[1];

    // Duplicate account check
    if account_a.key == account_b.key {
        return Err(ProgramError::InvalidArgument);
    }

    **account_a.lamports.borrow_mut() -= amount;
    **account_b.lamports.borrow_mut() += amount;
    Ok(())
}

Alternative Mitigations

  1. an account-validation framework constraints: Use the account-validation framework’s constraint system to enforce account distinctness declaratively.
#[derive(Accounts)]
pub struct Swap<'info> {
    #[account(mut, constraint = account_a.key() != account_b.key() @ ErrorCode::DuplicateAccount)]
    pub account_a: Account<'info, TokenAccount>,
    #[account(mut)]
    pub account_b: Account<'info, TokenAccount>,
}
  1. PDA-based design: Design account structures so that mutable accounts are derived from different seeds, making duplication impossible by construction.

  2. Read-then-write pattern: Read all account data into local variables, perform calculations, then write results back. This avoids interleaved read/write conflicts but still requires duplicate checks for correctness.

Common Mistakes

  • Checking only adjacent account pairs: If three or more mutable accounts are used, check all pairs, not just consecutive ones.
  • Missing duplicate check before CPI: CPI passes accounts to another program that may modify them. If a locally-modified account is also passed to CPI, the write order is unpredictable.
  • Using remaining_accounts without duplicate checks: Accounts in remaining_accounts can duplicate accounts in the typed account struct. Validate distinctness against all typed accounts.
  • Assuming PDA uniqueness without verification: PDAs with identical seeds but different bumps can resolve to different addresses, but always verify the canonical bump to prevent confusion.

References