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.
Recommended Fix
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
- Anchor constraints: Use Anchor’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>,
}
-
PDA-based design: Design account structures so that mutable accounts are derived from different seeds, making duplication impossible by construction.
-
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_accountswithout duplicate checks: Accounts inremaining_accountscan 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.