Sysvar Account Spoofing Remediation
How to prevent sysvar spoofing by validating account keys or using the Sysvar::get() API.
Sysvar Account Spoofing Remediation
Overview
Related Detector: Sysvar Account Spoofing
Sysvar spoofing vulnerabilities arise when a program reads system data from an account without verifying that the account is the real sysvar. The simplest fix is to use Sysvar::get() which reads directly from the runtime. When an account-based read is necessary, validate the account key against the canonical sysvar address.
Recommended Fix
Before (Vulnerable)
pub fn check_timelock(accounts: &[AccountInfo]) -> ProgramResult {
let clock_account = &accounts[2];
// VULNERABLE: no key validation
let data = clock_account.try_borrow_data()?;
let unix_timestamp = i64::from_le_bytes(data[32..40].try_into().unwrap());
if unix_timestamp < unlock_time {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
After (Fixed)
use solana_program::sysvar::clock::Clock;
use solana_program::sysvar::Sysvar;
pub fn check_timelock(_accounts: &[AccountInfo]) -> ProgramResult {
// FIXED: Sysvar::get() reads from the runtime -- no spoofable account
let clock = Clock::get()?;
if clock.unix_timestamp < unlock_time {
return Err(ProgramError::InvalidArgument);
}
Ok(())
}
Alternative Mitigations
1. Validate sysvar account key
When you need to pass the sysvar as an account (e.g., for CPI or for legacy compatibility):
use solana_program::sysvar;
pub fn process(accounts: &[AccountInfo]) -> ProgramResult {
let clock_account = &accounts[2];
// Validate key matches canonical Clock sysvar
if clock_account.key != &sysvar::clock::ID {
return Err(ProgramError::InvalidAccountData);
}
let clock = Clock::from_account_info(clock_account)?;
// Safe to use clock.unix_timestamp
Ok(())
}
2. Use Sysvar::from_account_info() (validates internally)
use solana_program::sysvar::Sysvar;
let clock = Clock::from_account_info(clock_account)?; // Validates key internally
let rent = Rent::from_account_info(rent_account)?;
This method checks the account key and owner before deserializing.
3. Anchor Sysvar<'info, T> type
Anchor validates the sysvar account automatically:
#[derive(Accounts)]
pub struct ProcessTimelock<'info> {
pub clock: Sysvar<'info, Clock>, // Validated at deserialization
}
pub fn process_timelock(ctx: Context<ProcessTimelock>) -> Result<()> {
let timestamp = ctx.accounts.clock.unix_timestamp;
// Safe -- Anchor validated the account
Ok(())
}
Common Mistakes
Mistake 1: Checking owner but not key
// WRONG: System program owns many accounts, not just sysvars
if clock_account.owner != &solana_program::system_program::ID {
return Err(ProgramError::InvalidAccountData);
}
Sysvars are owned by the Sysvar program (Sysvar1111111111111111111111111111111111111), but checking owner alone is insufficient. Check the specific sysvar key.
Mistake 2: Hardcoding sysvar data offsets without key validation
// Correctly reads the right offsets, but from a fake account
let slot = u64::from_le_bytes(data[0..8].try_into().unwrap());
let epoch = u64::from_le_bytes(data[16..24].try_into().unwrap());
Correct offset parsing is irrelevant if the data source is attacker-controlled. Always validate the account key first.
Mistake 3: Using Sysvar::get() for Instructions sysvar
// WRONG: Instructions sysvar does NOT support Sysvar::get()
// It must be passed as an account and key-validated
let instructions = Instructions::get()?; // This will fail
The Instructions sysvar requires an account because its data depends on the current transaction context. Use instructions::load_instruction_at_checked() with a key-validated account.