Sysvar Account Spoofing
Detects programs reading sysvar data from accounts without validating the account key matches the canonical sysvar address.
Sysvar Account Spoofing
Overview
Remediation Guide: How to Fix Sysvar Account Spoofing
The sysvar account spoofing detector identifies Solana programs that read data from accounts using sysvar access patterns (structured reads at known offsets) without verifying that the account’s key matches the canonical sysvar address. An attacker can create a fake account with data laid out to mimic a sysvar (Clock, Rent, Instructions, EpochSchedule) and pass it to the program, controlling the values the program reads.
This was the root cause of the $326M Wormhole bridge exploit in February 2022, where the attacker passed a fake Instructions sysvar account to bypass signature verification.
Why This Is an Issue
Solana sysvars provide system-level data (timestamps, rent parameters, instruction history). Programs that read this data from an account passed by the caller must verify the account key, because:
- An attacker creates a regular account and writes crafted data at the same offsets as the real sysvar
- The program reads the attacker’s account data and interprets it as sysvar values
- With a spoofed Clock sysvar, the attacker controls timestamps used for timelocks, vesting schedules, and interest calculations
- With a spoofed Rent sysvar, the attacker manipulates rent-exemption calculations
- With a spoofed Instructions sysvar, the attacker can bypass signature and instruction verification entirely
CWE mapping: CWE-290 (Authentication Bypass by Spoofing).
How to Resolve
// Before: Vulnerable -- reads Clock data from unverified account
pub fn process_vesting(accounts: &[AccountInfo]) -> ProgramResult {
let clock_account = &accounts[2];
// VULNERABLE: no key check -- attacker provides fake clock
let clock_data = clock_account.try_borrow_data()?;
let timestamp = i64::from_le_bytes(clock_data[32..40].try_into().unwrap());
if timestamp > vesting_end_time {
release_tokens(accounts)?;
}
Ok(())
}
// After: Use Sysvar::get() or validate account key
pub fn process_vesting(accounts: &[AccountInfo]) -> ProgramResult {
// FIXED: use Sysvar::get() -- reads directly from runtime, no account needed
let clock = Clock::get()?;
if clock.unix_timestamp > vesting_end_time {
release_tokens(accounts)?;
}
Ok(())
}
Examples
Vulnerable Code
use solana_program::account_info::AccountInfo;
pub fn verify_instruction(accounts: &[AccountInfo]) -> ProgramResult {
let instructions_account = &accounts[3];
// No validation that this is the real Instructions sysvar
let instruction_data = instructions_account.try_borrow_data()?;
let num_instructions = u16::from_le_bytes(
instruction_data[0..2].try_into().unwrap()
);
// Attacker controls instruction_data -- forges verification
if num_instructions >= 2 {
// Assumes a prior Ed25519 signature verification instruction exists
process_verified_action(accounts)?;
}
Ok(())
}
Fixed Code
use solana_program::sysvar::instructions;
pub fn verify_instruction(accounts: &[AccountInfo]) -> ProgramResult {
let instructions_account = &accounts[3];
// FIXED: validate this is the real Instructions sysvar
if instructions_account.key != &instructions::ID {
return Err(ProgramError::InvalidAccountData);
}
// Now safe to read instruction data
let current_ix = instructions::load_current_index_checked(instructions_account)?;
let prev_ix = instructions::load_instruction_at_checked(
(current_ix - 1) as usize,
instructions_account,
)?;
// Verify the previous instruction was an Ed25519 signature check
if prev_ix.program_id != solana_program::ed25519_program::ID {
return Err(ProgramError::InvalidInstructionData);
}
process_verified_action(accounts)?;
Ok(())
}
Sample Sigvex Output
{
"detector_id": "sysvar-account-spoofing",
"severity": "critical",
"confidence": 0.88,
"description": "Account v3 is used with sysvar-like read patterns (4 structured reads at sequential offsets) but has no CheckKey validation against any canonical sysvar address. An attacker can pass a crafted account to control the values read by the program.",
"location": { "function": "verify_instruction", "offset": 2 },
"attack_chain": [
"Create fake account with crafted sysvar-format data",
"Pass fake account where the program expects a sysvar",
"Program reads attacker-controlled values as system data",
"Attacker bypasses time checks, rent checks, or instruction verification"
]
}
Detection Methodology
The detector combines access pattern analysis with dataflow validation tracking:
- Account read collection: Identifies all
AccountDataread operations and groups them by source account variable. - Sysvar pattern recognition: Accounts with multiple structured reads at sequential offsets matching known sysvar layouts (Clock: 8+8+8+8+8 bytes, Rent: 8+8+1 bytes, Instructions: variable) are flagged as potential sysvar reads.
- Validation check: Uses
SharedDataflowStateto verify whether aCheckKeyvalidation against a canonical sysvar address exists for the account in any dominating block. - Alias tracking: Variable aliases are tracked so that
let clock = accounts[2]followed by a key check onclockis recognized. - Duplicate suppression: Each account is reported at most once, even if multiple reads occur.
Limitations
False positives:
- Programs that validate the sysvar account in a separate instruction within the same transaction.
- Programs that use
Sysvar::from_account_info()which internally validates the key (the detector may not recognize this pattern if it is not inlined).
False negatives:
- Sysvar reads through non-standard deserialization patterns that do not match known offset layouts.
- Programs that store sysvar data in intermediate accounts for later use.
Related Detectors
- Clock Account Spoofing — specifically targets Clock sysvar spoofing for time-dependent logic
- Missing Owner Check — validates account ownership
- Type Cosplay — accounts deserialized as the wrong type