Deserialization Attack Exploit Generator
Sigvex exploit generator that validates unsafe deserialization vulnerabilities in Solana programs where crafted account data causes incorrect state interpretation, type confusion, or panic in the deserialization layer.
Deserialization Attack Exploit Generator
Overview
The deserialization attack exploit generator validates findings from the unsafe-deserialization detector by classifying unsafe deserialization patterns in Solana programs. The vulnerability involves supplying crafted account data that causes the program to misinterpret account state, bypass discriminator checks, or trigger panics during data parsing. This class is assessed with confidence 0.65 through static analysis — dynamic simulation is limited because deserialization failures typically produce panics or explicit errors rather than silent state corruption.
Solana programs deserialize account data from raw bytes into structured types. If a program uses unsafe deserialization — bypassing Anchor’s discriminator checks, using raw try_from_slice on user-controlled data, or failing to validate data length — an attacker can craft account data that the program misinterprets. This class of vulnerability spans three categories: discriminator bypass (passing crafted bytes that match an unexpected type), panic-causing inputs (data too short to deserialize), and semantic manipulation (valid bytes but with crafted field values).
Severity score: 65/100 (High).
Note: Exploit generation in Sigvex is for vulnerability validation purposes only.
Attack Scenario
Discriminator bypass:
- An Anchor program expects a
Vaultaccount at a specific position. - The discriminator for
Vaultis the first 8 bytes:sha256("account:Vault")[..8]. - The program uses raw
try_from_sliceinstead ofAccount<'info, Vault>, skipping the discriminator check. - An attacker creates an account whose first
size_of::<Vault>()bytes are crafted to represent a favorable state. - The program reads the crafted data as a
Vaultwith an inflated balance.
Length underrun panic:
- A program uses
Vault::try_from_slice(account.data.borrow())without checking data length. - An attacker passes an account with 0 bytes of data.
try_from_slicepanics because the data is too short.- The panic causes the transaction to fail — a denial of service.
- If the account is used in a critical path, the program is bricked for all users.
Field manipulation via crafted bytes:
- A program deserializes a
Configstruct:{ authority: Pubkey, fee_bps: u16, paused: bool }. - The program does not use Anchor’s typed accounts — it uses raw deserialization.
- An attacker creates an account with crafted bytes:
authoritybytes set to attacker’s key,fee_bpsset to 10000 (100%). - The program reads the attacker’s key as
authorityand uses 100% fee.
Exploit Mechanics
The engine maps unsafe-deserialization detector findings to the deserialization attack exploit class. Assessment is performed through static analysis of the program’s deserialization code paths, examining account data handling before and after instruction entry points.
Key deserialization risks:
try_from_slicewithout length check → panic on short dataAccountInfowithout discriminator check → type confusionunsafememory transmutation → undefined behavior- Missing bounds validation on deserialized integers → semantic exploitation
// VULNERABLE: Raw deserialization without discriminator check
use anchor_lang::prelude::*;
#[program]
mod vulnerable_program {
pub fn process_vault(ctx: Context<ProcessVault>) -> Result<()> {
// VULNERABLE: try_from_slice bypasses Anchor's discriminator check
let vault = Vault::try_from_slice(&ctx.accounts.vault.data.borrow())
.map_err(|_| error!(ErrorCode::InvalidAccount))?;
// vault.balance could be crafted by an attacker!
process(vault.balance);
Ok(())
}
}
#[derive(Accounts)]
pub struct ProcessVault<'info> {
/// CHECK: Using raw deserialization — vulnerable!
pub vault: AccountInfo<'info>,
}
// SECURE: Use Anchor's typed accounts for automatic discriminator + owner verification
#[derive(Accounts)]
pub struct ProcessVaultSafe<'info> {
// Account<'info, Vault> automatically:
// 1. Checks discriminator (first 8 bytes match Vault::discriminator())
// 2. Checks program ownership
// 3. Checks data length >= size_of::<Vault>()
pub vault: Account<'info, Vault>,
}
// SECURE: Manual deserialization with all checks
fn safe_deserialize_vault(account: &AccountInfo, program_id: &Pubkey) -> Result<Vault> {
// 1. Check program ownership
if account.owner != program_id {
return Err(ErrorCode::InvalidAccountOwner.into());
}
let data = account.try_borrow_data()?;
// 2. Check data length
const DISCRIMINATOR_LEN: usize = 8;
const VAULT_SIZE: usize = std::mem::size_of::<Vault>();
if data.len() < DISCRIMINATOR_LEN + VAULT_SIZE {
return Err(ErrorCode::AccountDataTooSmall.into());
}
// 3. Check discriminator
let expected_discriminator = Vault::discriminator();
if data[..DISCRIMINATOR_LEN] != expected_discriminator {
return Err(ErrorCode::InvalidAccountDiscriminator.into());
}
// 4. Deserialize (skip discriminator bytes)
let vault = Vault::try_from_slice(&data[DISCRIMINATOR_LEN..])?;
// 5. Validate deserialized field ranges
require!(vault.fee_bps <= 10000, ErrorCode::InvalidFee);
Ok(vault)
}
Remediation
- Detector: Deserialization Attack Detector
- Remediation Guide: Deserialization Attack Remediation
Use Anchor’s typed account system exclusively for account deserialization:
// Always use Account<'info, T>, never AccountInfo<'info> for typed data
pub vault: Account<'info, Vault>,
// For accounts owned by other programs (e.g., SPL Token):
use anchor_spl::token::TokenAccount;
pub token_account: Account<'info, TokenAccount>,
// When AccountInfo is required, add explicit validation:
#[account(
owner = token_program.key(), // Checks program ownership
)]
/// CHECK: Validated by owner constraint and manual discriminator check below
pub raw_account: AccountInfo<'info>,
For custom deserialization:
- Always check program ownership before deserializing.
- Always verify the 8-byte discriminator.
- Always verify data length is at least
8 + size_of::<T>(). - After deserialization, validate all field values are within expected bounds.
- Use
checkedortry_fromfor numeric field parsing to avoid panics.