Skip to main content
Sigvex

Sysvar Substitution

Detects unverified sysvar account usage that could allow attackers to substitute fake system data.

Sysvar Substitution

Overview

Remediation Guide: How to Fix Sysvar Substitution Issues

The sysvar substitution detector identifies Solana programs that read data from accounts in a pattern consistent with sysvar access (Clock, Rent, EpochSchedule) without first verifying the account’s key matches the known sysvar address. If a program does not validate that a sysvar account is the real sysvar, an attacker can pass a fake account with arbitrary data, manipulating timestamps, rent calculations, or epoch information.

Sigvex tracks key verification statements (CheckKey) and data access patterns that resemble sysvar reads (characteristic offsets for Clock, Rent, and EpochSchedule sysvars). Accounts accessed with sysvar-like patterns without key verification are flagged. CWE mapping: CWE-345 (Insufficient Verification of Data Authenticity).

Why This Is an Issue

Sysvar accounts provide trusted system information:

  • Clock: Current slot, epoch, and unix timestamp. Manipulating these can bypass time locks, vesting schedules, and expiration checks.
  • Rent: Rent exemption threshold. Manipulating this can cause incorrect rent calculations, potentially draining accounts.
  • EpochSchedule: Epoch boundaries and warmup periods. Manipulating this can confuse staking logic.

Without key verification, an attacker creates a data account mimicking the sysvar layout and passes it instead:

  • Time manipulation: A fake Clock sysvar with a future timestamp can unlock time-locked funds prematurely.
  • Rent bypass: A fake Rent sysvar with a zero threshold can make the program believe any account is rent-exempt.
  • Oracle manipulation: Programs that use sysvar data as oracle input can be fed arbitrary values.

How to Resolve

Native Rust

use solana_program::{
    account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult,
    sysvar::Sysvar,
};

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

    // Method 1: Use the type-safe Sysvar interface (recommended)
    let clock = Clock::from_account_info(clock_account)?;
    // This internally validates the account key

    // Method 2: Manual key validation
    if clock_account.key != &solana_program::sysvar::clock::id() {
        return Err(ProgramError::InvalidAccountData);
    }

    Ok(())
}

Anchor

#[derive(Accounts)]
pub struct CheckExpiry<'info> {
    // Sysvar<Clock> validates the account key automatically
    pub clock: Sysvar<'info, Clock>,
}

Examples

Vulnerable Code

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

pub fn release_vested(accounts: &[AccountInfo]) -> ProgramResult {
    let clock_account = &accounts[0]; // Not validated!
    let vesting = &accounts[1];

    // Read timestamp from unvalidated "clock" account
    let data = clock_account.data.borrow();
    let timestamp = i64::from_le_bytes(data[32..40].try_into()?);

    // Attacker passes fake account with future timestamp
    if timestamp > UNLOCK_TIME {
        // Releases funds prematurely!
        release_funds(vesting)?;
    }
    Ok(())
}

Fixed Code

use solana_program::{
    account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult,
    sysvar::Sysvar,
};

pub fn release_vested(accounts: &[AccountInfo]) -> ProgramResult {
    let clock_account = &accounts[0];
    let vesting = &accounts[1];

    // Use type-safe interface that validates the account key
    let clock = Clock::from_account_info(clock_account)?;

    if clock.unix_timestamp > UNLOCK_TIME {
        release_funds(vesting)?;
    }
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "sysvar-substitution",
  "severity": "high",
  "confidence": 0.82,
  "description": "Account v0 is accessed with a Clock sysvar data pattern (offsets 0, 8, 16, 24, 32) without key verification. An attacker can pass a fake account with arbitrary data.",
  "location": { "function": "release_vested", "offset": 1 }
}

Detection Methodology

The detector uses pattern-based sysvar identification:

  1. Key verification collection: Tracks all CheckKey statements to identify accounts whose keys have been verified.
  2. Sysvar pattern matching: Identifies data accesses at characteristic offsets for known sysvars (Clock: offsets 0, 8, 16, 24, 32; Rent: offsets 0, 8, 16; EpochSchedule: offsets 0, 8, 16, 17).
  3. Gap detection: Reports accounts accessed with sysvar patterns that lack key verification. Only the first access per account is reported to reduce noise.

Context modifiers:

  • Anchor programs: confidence reduced (Anchor’s Sysvar<T> validates account keys)
  • Admin/initialization functions: confidence reduced by 40%
  • Read-only/view functions: confidence reduced by 50%

Limitations

False positives:

  • Account data reads at offsets that coincidentally match sysvar patterns but are unrelated to sysvar access will be flagged. The detector relies on offset heuristics, not semantic analysis.
  • Programs that validate sysvar keys through indirect means (e.g., checking owner equals the sysvar program) may be flagged.

False negatives:

  • Custom sysvar-like accounts (e.g., oracle feeds with similar layouts) are not tracked.
  • Sysvar access through the Sysvar::get() function (which does not require an account) is not relevant and correctly ignored.

References