Skip to main content
Sigvex

Uninitialized Account

Detects usage of uninitialized accounts before proper initialization.

Uninitialized Account

Overview

Remediation Guide: How to Fix Uninitialized Account

The uninitialized account detector identifies Solana program functions that read account data before the account has been properly initialized. An attacker can pass a freshly allocated account whose data is all zeros or attacker-controlled, leading to type confusion from unset discriminators or exploitable invalid state.

Sigvex tracks initialization events (StoreAccountData at offset 0) and account data reads, reporting any read that occurs for an account not yet initialized and not provided as a parameter (parameters represent existing on-chain accounts).

Why This Is an Issue

  • Type confusion: reading an uninitialized discriminator (all zeros) may cause the program to misidentify the account type, processing it with incorrect field layouts.
  • Attacker-controlled state: the attacker can pre-fill the account with arbitrary data before passing it to the program, injecting malicious values into any field the program reads.
  • Zero-value exploits: financial fields default to zero in uninitialized accounts, enabling drain attacks where the program interprets zero as a valid balance.

CWE mapping: CWE-908 (Use of Uninitialized Resource).

How to Resolve

Native Solana

pub fn process(accounts: &[AccountInfo]) -> ProgramResult {
    let account = &accounts[0];

    // Verify account is initialized
    let data = account.data.borrow();
    if data.is_empty() || data[0..8] == [0u8; 8] {
        return Err(ProgramError::UninitializedAccount);
    }

    // Verify discriminator
    require!(data[0..8] == MY_DISCRIMINATOR, InvalidAccountData);

    // Now safe to deserialize
    let state = MyState::try_from_slice(&data[8..])?;
    // ...
}

Anchor

// Anchor deserializes and validates discriminator automatically
#[derive(Accounts)]
pub struct Process<'info> {
    #[account(has_one = authority)]
    pub state: Account<'info, MyState>,
    pub authority: Signer<'info>,
}

Examples

Vulnerable

pub fn process(accounts: &[AccountInfo]) -> ProgramResult {
    let account = &accounts[0];
    let data = account.data.borrow();
    // No initialization check — reads potentially uninitialized data
    let authority = Pubkey::try_from_slice(&data[8..40])?;
    transfer_to(authority, amount)?;
    Ok(())
}

Fixed

pub fn process(accounts: &[AccountInfo]) -> ProgramResult {
    let account = &accounts[0];
    require!(!account.data_is_empty(), UninitializedAccount);
    let data = account.data.borrow();
    require!(data[0..8] == MY_DISCRIMINATOR, InvalidAccountData);
    let authority = Pubkey::try_from_slice(&data[8..40])?;
    transfer_to(authority, amount)?;
    Ok(())
}

JSON Finding

{
  "detector": "uninitialized-account",
  "severity": "High",
  "confidence": 0.70,
  "title": "Uninitialized Account Access",
  "description": "Account data is read before initialization.",
  "cwe": [908]
}

Detection Methodology

The detector pre-populates the initialized set with parameter-derived accounts (since they represent existing on-chain state) and CPI-referenced accounts. It then scans for StoreAccountData at offset 0 as initialization markers, and flags AccountData reads from accounts not in the initialized set.

Limitations

  • Parameter accounts are assumed initialized, which may not hold if the caller passes a freshly created account.
  • The detector only recognizes StoreAccountData at offset 0 as initialization. Custom initialization patterns may be missed.
  • Anchor programs with init constraints receive significantly reduced confidence since Anchor handles initialization atomically.

References