Account Resurrection Remediation
How to fix account resurrection vulnerabilities from PDA re-derivation.
Account Resurrection Remediation
Overview
Detector Reference: Account Resurrection
This guide explains how to prevent closed accounts from being resurrected at the same address via PDA re-derivation with the same seeds. Resurrection allows attackers to bypass state checks by replacing a closed account with a fresh one at the identical address.
Recommended Fix
Always zero all account data before draining lamports, and include dynamic seeds (nonces or counters) in PDA derivation to prevent address reuse:
pub fn close_account(ctx: Context<CloseAccount>) -> Result<()> {
let account = &ctx.accounts.my_account;
// 1. Zero all account data (invalidates discriminator)
account.to_account_info().data.borrow_mut().fill(0);
// 2. Transfer lamports to destination
let dest = &ctx.accounts.destination;
**dest.to_account_info().try_borrow_mut_lamports()? += account.to_account_info().lamports();
**account.to_account_info().try_borrow_mut_lamports()? = 0;
Ok(())
}
In Anchor, the close constraint handles both zeroing and lamport transfer:
#[account(mut, close = destination)]
pub my_account: Account<'info, MyAccount>,
Alternative Mitigations
- Tombstone pattern: maintain a separate registry of closed account addresses. Before accepting an account, check it is not in the tombstone set.
- Epoch-based PDA seeds: include the current epoch or slot in PDA seeds so that old addresses cannot be re-derived after epoch transitions.
- Nonce counter: store a monotonically increasing counter in a global state account and include it in PDA seeds. Increment on close.
Common Mistakes
- Draining lamports without zeroing data: the account data persists until the runtime garbage-collects it, leaving valid discriminators that pass type checks.
- Relying solely on static PDA seeds: static seeds like
[b"vault", user.key().as_ref()]are deterministic and allow anyone to re-derive the same address. - Forgetting cross-program references: even if your program handles close correctly, other programs may cache the account address and interact with the resurrected account.