Skip to main content
Sigvex

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.

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

  1. Tombstone pattern: maintain a separate registry of closed account addresses. Before accepting an account, check it is not in the tombstone set.
  2. Epoch-based PDA seeds: include the current epoch or slot in PDA seeds so that old addresses cannot be re-derived after epoch transitions.
  3. 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.

References