Stale Account Data Remediation
How to fix stale account data usage after CPI calls.
Stale Account Data Remediation
Overview
Related Detector: Stale Account Data After CPI
Stale account data vulnerabilities occur when programs cache account data before a CPI and use the stale values after the CPI returns. The fix is to always reload account data after any CPI that could modify the relevant accounts.
Recommended Fix
Before (Vulnerable)
let balance = get_balance(vault)?;
invoke(&cpi_ix, accounts)?;
// Using stale balance
if balance >= withdrawal_amount { /* ... */ }
After (Fixed)
invoke(&cpi_ix, accounts)?;
// Re-read fresh data after CPI
let balance = get_balance(vault)?;
if balance >= withdrawal_amount { /* ... */ }
Alternative Mitigations
1. Anchor reload pattern
// After CPI, reload the account to get fresh data
ctx.accounts.vault.reload()?;
let fresh_amount = ctx.accounts.vault.amount;
2. Checks-effects-interactions ordering
Perform all reads and checks before CPI, eliminating the need for post-CPI reads:
// CHECKS: read and validate before CPI
let balance = get_balance(vault)?;
require!(balance >= amount, InsufficientFunds);
// EFFECTS: update state before CPI
set_balance(vault, balance - amount)?;
// INTERACTIONS: CPI last
invoke(&transfer_ix, accounts)?;
3. Post-CPI assertion pattern
Re-read and assert expected state after CPI:
let balance_before = get_balance(vault)?;
invoke(&deposit_ix, accounts)?;
// Assert the expected post-CPI state
let balance_after = get_balance(vault)?;
require!(
balance_after == balance_before + deposit_amount,
ErrorCode::UnexpectedBalance
);
Common Mistakes
Mistake 1: Caching in a local variable and forgetting to refresh
let cached = vault.data.borrow()[0..8]; // Cached before CPI
invoke(&ix, accounts)?;
// cached is stale -- must re-borrow vault.data
Mistake 2: Reading lamports before CPI
let lamports = vault.lamports(); // Stale after CPI
invoke(&transfer_ix, accounts)?;
if lamports > 0 { /* wrong -- CPI may have drained lamports */ }
Lamports are also account data that can change during CPI.
Mistake 3: Only reloading some accounts
ctx.accounts.vault.reload()?; // Reloaded
// But also need to reload other accounts modified by CPI
// ctx.accounts.user_token.reload()?; // Missing!