Shared Mutable Race
Detects race conditions from shared mutable account access where the same account is modified through multiple paths without duplicate checks.
Shared Mutable Race
Overview
Remediation Guide: How to Fix Shared Mutable Race Conditions
The shared mutable race detector identifies Solana programs where the same mutable account is written to through multiple execution paths or through both direct writes and CPI calls without proper duplicate account validation. In Solana, accounts are passed as a flat array, and the program is responsible for ensuring that the same account is not passed in multiple mutable positions. Without duplicate checks, concurrent modifications can corrupt state.
Sigvex tracks all write operations (direct StoreAccountData and CPI writes) for each account variable, along with duplicate account validation patterns. Accounts that are written to multiple times without validation or that are modified both locally and via CPI are flagged. CWE mapping: CWE-362 (Concurrent Execution Using Shared Resource with Improper Synchronization).
Why This Is an Issue
Shared mutable access creates several attack vectors:
- Double-spend via aliasing: If the same token account is passed as both source and destination in a transfer, balance calculations can produce incorrect results.
- State corruption: Multiple writes to the same account offset from different code paths can overwrite each other, leaving the account in an inconsistent state.
- CPI write conflicts: A local write followed by a CPI that also writes to the same account can produce unpredictable results, since the CPI may read stale pre-write data.
- Reentrancy-like effects: While Solana prevents true reentrancy, shared mutable accounts in CPI chains can produce similar state confusion.
How to Resolve
Native Rust
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn safe_transfer(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let source = &accounts[0];
let destination = &accounts[1];
// Check for duplicate accounts
if source.key == destination.key {
return Err(ProgramError::InvalidArgument);
}
**source.lamports.borrow_mut() -= amount;
**destination.lamports.borrow_mut() += amount;
Ok(())
}
Anchor
#[derive(Accounts)]
pub struct SafeTransfer<'info> {
#[account(mut, constraint = source.key() != destination.key() @ ErrorCode::DuplicateAccount)]
pub source: Account<'info, TokenAccount>,
#[account(mut)]
pub destination: Account<'info, TokenAccount>,
}
Examples
Vulnerable Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn process_rewards(accounts: &[AccountInfo]) -> ProgramResult {
let reward_pool = &accounts[0];
let user_account = &accounts[1];
// Write 1: Update reward pool
let mut pool_data = reward_pool.data.borrow_mut();
pool_data[0..8].copy_from_slice(&new_pool_balance.to_le_bytes());
drop(pool_data);
// CPI that also modifies reward_pool -- potential conflict
solana_program::program::invoke(
&update_rewards_instruction(reward_pool.key),
&[reward_pool.clone(), user_account.clone()],
)?;
// No duplicate check -- if user_account == reward_pool, state is corrupted
Ok(())
}
Fixed Code
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn process_rewards(accounts: &[AccountInfo]) -> ProgramResult {
let reward_pool = &accounts[0];
let user_account = &accounts[1];
// Duplicate account check
if reward_pool.key == user_account.key {
return Err(ProgramError::InvalidArgument);
}
let mut pool_data = reward_pool.data.borrow_mut();
pool_data[0..8].copy_from_slice(&new_pool_balance.to_le_bytes());
drop(pool_data);
solana_program::program::invoke(
&update_rewards_instruction(reward_pool.key),
&[reward_pool.clone(), user_account.clone()],
)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "shared-mutable-race",
"severity": "high",
"confidence": 0.78,
"description": "Account v0 has multiple write operations (direct write at block 0 stmt 1, CPI write at block 0 stmt 3) without duplicate account validation. Concurrent modifications through different paths can corrupt account state.",
"location": { "function": "process_rewards", "offset": 1 }
}
Detection Methodology
The detector performs a two-pass analysis:
- Operation collection: Tracks all write operations per account variable, categorized as direct writes (
StoreAccountData) or CPI writes (InvokeCpi). Also collects duplicate account validation patterns (comparison operations). - Race detection: Reports accounts with multiple writes lacking validation, and accounts modified both locally and via CPI without duplicate checks.
Context modifiers:
- Anchor programs: confidence reduced by 50% (Anchor’s type system provides some protection)
- Admin/initialization functions: confidence reduced by 40%
- Read-only/view functions: not flagged (no mutable operations)
Limitations
False positives:
- Sequential writes to the same account that are intentional (e.g., initializing multiple fields) may be flagged as multiple writes.
- Programs that validate account distinctness through indirect means (e.g., PDA derivation with different seeds guarantees distinct accounts) may be flagged.
False negatives:
- Race conditions between separate transactions modifying the same account are not detectable at the program level.
- Duplicate accounts passed via
remaining_accountsthat are not tracked as individual variables may be missed.
Related Detectors
- Duplicate Mutable Accounts — detects duplicate accounts passed in mutable positions
- Account Alias Attack — detects role confusion through account aliasing