Readonly CPI Write Bypass Remediation
Overview
Related Detector: Readonly CPI Write Bypass
Readonly accounts can be modified by CPI target programs that own them. The fix is to either require writable for accounts forwarded to CPI, or reload and re-validate account data after CPI returns.
Impact. A program that reads an account’s balance, owner, or invariant, makes a CPI, and then enforces a check against the pre-CPI snapshot is trusting data the callee may have changed. An attacker whose program is the CPI target mutates the account mid-transaction and the caller validates against the stale state — turning a “readonly” assumption into an exploitable window that can defeat balance or authorization checks.
Recommended Fix
// Option 1: Require writable
require!(account.is_writable, InvalidArgument);
invoke(&ix, &[account.clone()])?;
// Option 2: Re-validate after CPI
invoke(&ix, &[account.clone()])?;
ctx.accounts.my_account.reload()?;
validate_invariants(&ctx.accounts.my_account)?;
Common Mistakes
Mistake: Trusting Pre-CPI State
let balance = account.lamports();
invoke(&external_ix, &[account.clone()])?;
// WRONG: balance may have changed
require!(balance >= minimum, Insufficient);
Always re-read account state after CPI.