Account Close Discriminator Remediation
How to fix missing discriminator zeroing on account close.
Account Close Discriminator Remediation
Overview
Detector Reference: Account Close Discriminator
This guide explains how to properly zero the discriminator field when closing accounts to prevent resurrection attacks.
Recommended Fix
Zero the discriminator (first 8 bytes) before draining lamports:
// Step 1: Zero discriminator
let mut data = account.data.borrow_mut();
data[0..8].fill(0);
drop(data);
// Step 2: Optionally zero all data for defense in depth
// account.data.borrow_mut().fill(0);
// Step 3: Drain lamports
**destination.try_borrow_mut_lamports()? += account.lamports();
**account.try_borrow_mut_lamports()? = 0;
For Anchor, use the close constraint which handles this automatically:
#[account(mut, close = destination)]
pub my_account: Account<'info, MyAccount>,
Alternative Mitigations
- Zero all data:
account.data.borrow_mut().fill(0)zeroes everything, not just the discriminator. This is more defensive but costs more compute. - Tombstone registry: maintain a separate account that records closed PDA addresses, checked during initialization to prevent resurrection.
- Dynamic PDA seeds: include a nonce in PDA seeds that increments on close, making the old address unreachable.
Common Mistakes
- Zeroing after lamport drain: zero the discriminator before draining lamports. The order matters because the account may be reclaimed between the drain and the zero.
- Zeroing only 4 bytes: Anchor uses 8-byte discriminators. Zeroing fewer bytes leaves a partial discriminator that could still pass weak validation.
- Relying on runtime garbage collection: the runtime zeroes account data eventually, but the timing is not guaranteed within the same slot.