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
StoreAccountDataat offset 0 as initialization. Custom initialization patterns may be missed. - Anchor programs with
initconstraints receive significantly reduced confidence since Anchor handles initialization atomically.
Related Detectors
- Missing Empty Account Check - detects initialization without empty validation.
- Unchecked Deserialization - detects deserialization without error handling.