Generic Init Frontrun
Detects initialization patterns vulnerable to frontrunning attacks.
Generic Init Frontrun
Overview
Remediation Guide: How to Fix Generic Init Frontrun
The generic init frontrun detector identifies account initialization patterns where writes occur at discriminator offsets (0 or 8) without prior key or PDA validation. An attacker who monitors the mempool can front-run the initialization by creating an account at the expected address with malicious initial state, causing the program to skip its initialization branch and operate on attacker-controlled data.
Unlike the token init race detector which focuses on SPL token accounts, this detector covers generic program accounts, config accounts, and PDAs.
Sigvex uses dataflow analysis to track validation state (key checks, owner checks, signer checks) and flags StoreAccountData writes at initialization offsets where the target account lacks sufficient validation.
Why This Is an Issue
- State injection: the attacker pre-populates the account with values that make downstream logic behave in their favor (e.g., setting themselves as the authority).
- Initialization bypass: if the program checks “is this account empty?” and the attacker pre-fills it, the init logic is skipped entirely, leaving the account in an attacker-chosen state.
- PDA confusion: for non-PDA accounts, any wallet can create an account at an arbitrary address. For PDAs, the attack requires finding the same seeds.
CWE mapping: CWE-362 (Concurrent Execution Using Shared Resource with Improper Synchronization).
How to Resolve
Native Solana
pub fn initialize(accounts: &[AccountInfo], program_id: &Pubkey) -> ProgramResult {
let account = &accounts[0];
// Validate PDA derivation
let (expected_key, bump) = Pubkey::find_program_address(
&[b"config", authority.as_ref()],
program_id,
);
require!(*account.key == expected_key, InvalidAccountAddress);
// Verify account is empty
require!(account.data.borrow()[0..8] == [0u8; 8], AlreadyInitialized);
// Now safe to initialize
let mut data = account.data.borrow_mut();
data[0..8].copy_from_slice(MY_DISCRIMINATOR);
// ...
}
Anchor
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = user, space = 8 + 64, seeds = [b"config", user.key().as_ref()], bump)]
pub config: Account<'info, Config>,
#[account(mut)]
pub user: Signer<'info>,
pub system_program: Program<'info, System>,
}
Examples
Vulnerable
pub fn initialize(accounts: &[AccountInfo]) -> ProgramResult {
let account = &accounts[0];
let mut data = account.data.borrow_mut();
// No key or PDA validation — anyone can front-run
data[0..8].copy_from_slice(MY_DISCRIMINATOR);
data[8..40].copy_from_slice(authority.as_ref());
Ok(())
}
Fixed
let (expected, _bump) = Pubkey::find_program_address(&[b"config"], program_id);
require!(*account.key == expected, InvalidAddress);
require!(account.data.borrow()[0..8] == [0u8; 8], AlreadyInit);
JSON Finding
{
"detector": "generic-init-frontrun",
"severity": "High",
"confidence": 0.75,
"title": "Initialization Frontrunning Risk",
"description": "Account is initialized without validating its key or verifying it is a legitimate PDA.",
"cwe": [362]
}
Detection Methodology
The detector uses shared dataflow state to track validation events (key checks, owner checks, signer checks) across basic blocks. It collects accounts appearing in data-length comparisons (initialization pattern indicators) and flags StoreAccountData writes at offsets 0 or 8 where the target account lacks key or owner validation. Confidence is reduced for PDA-derived accounts and for functions with signer checks.
Limitations
- PDA-derived accounts receive reduced confidence but are not excluded, since PDAs with user-controlled seeds may still be frontrunnable.
- The detector cannot determine whether the account address was validated in a prior instruction within the same transaction.
- Signer checks reduce confidence but do not eliminate findings, since the signer may not protect the specific initialization path.
Related Detectors
- Token Init Race - detects initialization races specific to token accounts.
- Missing Empty Account Check - detects missing empty validation.