Missing Empty Account Check Remediation
How to fix missing empty account validation before initialization.
Missing Empty Account Check Remediation
Overview
Detector Reference: Missing Empty Account Check
This guide explains how to fix initialization functions that write to accounts without first verifying the account is empty. Without this check, attackers can re-initialize accounts to overwrite authority keys, reset balances, or corrupt state.
Recommended Fix
Before writing initialization data, verify the account’s discriminator bytes are all zeros:
pub fn initialize(accounts: &[AccountInfo], authority: Pubkey) -> ProgramResult {
let account = &accounts[0];
let data = account.data.borrow();
// Verify account is truly empty
if data.len() >= 8 && data[0..8] != [0u8; 8] {
return Err(ProgramError::AccountAlreadyInitialized);
}
drop(data);
let mut data = account.data.borrow_mut();
data[0..8].copy_from_slice(MY_DISCRIMINATOR);
data[8..40].copy_from_slice(authority.as_ref());
Ok(())
}
For Anchor programs, use the init constraint which atomically creates and initializes accounts:
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 32)]
pub state: Account<'info, MyState>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
Alternative Mitigations
- Check data length: if the account’s data length is zero, it has not been allocated and is safe to initialize.
- Check owner: verify the account is still owned by the system program (indicating it has not been claimed by your program).
- Use
data_is_empty(): Solana’sAccountInfo::data_is_empty()returns true for zero-length accounts.
if !account.data_is_empty() {
return Err(ProgramError::AccountAlreadyInitialized);
}
Common Mistakes
- Checking only the first byte: a single-byte check can miss partially initialized accounts. Always check the full 8-byte discriminator.
- Using
init_if_neededwithout ownership validation: Anchor’sinit_if_neededskips initialization for existing accounts but does not validate who created them. Pair it with an owner constraint. - Placing the check after the write: the empty check must execute before any write to the account. Placing it after is ineffective.