Account Use After Close Remediation
How to fix use-after-close vulnerabilities in Solana programs.
Account Use After Close Remediation
Overview
Detector Reference: Account Use After Close
This guide explains how to fix patterns where accounts are accessed after being logically closed, preventing stale reads, invalid writes, and CPI hazards.
Recommended Fix
Restructure your instruction handler to perform all account operations before closing:
pub fn process_and_close(accounts: &[AccountInfo]) -> ProgramResult {
let account = &accounts[0];
let destination = &accounts[1];
// Phase 1: Read and process
let data = MyStruct::try_from_slice(&account.data.borrow())?;
let result = process(data)?;
// Phase 2: Close (always last)
account.data.borrow_mut().fill(0);
**destination.try_borrow_mut_lamports()? += account.lamports();
**account.try_borrow_mut_lamports()? = 0;
Ok(())
}
Alternative Mitigations
- Separate instructions: split close into a dedicated instruction that only closes, with no subsequent processing.
- Anchor close constraint: use
#[account(mut, close = destination)]which handles closure at the framework level after your handler completes. - Guard flag: set a local boolean after close and check it before any subsequent access.
Common Mistakes
- Logging after close: even read-only operations like logging account data after closure access invalid state.
- CPI after close: passing a closed account to another program via CPI is a use-after-close even though the data technically still exists in the transaction.
- Partial close: zeroing only the discriminator but not draining lamports creates an inconsistent state where the account appears closed to some checks but not others.