Sysvar Substitution Remediation
How to fix unverified sysvar account usage that allows data spoofing.
Remediating Sysvar Substitution
Overview
Related Detector: Sysvar Substitution
When sysvar accounts (Clock, Rent, EpochSchedule) are read without verifying their key matches the known sysvar address, attackers can pass fake accounts with arbitrary data. The fix is to use the type-safe Sysvar::from_account_info() interface or manually validate the account key before reading.
Recommended Fix
Before (Vulnerable)
use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};
pub fn check_time(accounts: &[AccountInfo]) -> ProgramResult {
let clock = &accounts[0]; // Unvalidated!
let data = clock.data.borrow();
let timestamp = i64::from_le_bytes(data[32..40].try_into()?);
// Attacker controls timestamp
Ok(())
}
After (Fixed)
use solana_program::{
account_info::AccountInfo, clock::Clock, entrypoint::ProgramResult,
sysvar::Sysvar,
};
pub fn check_time(accounts: &[AccountInfo]) -> ProgramResult {
let clock_account = &accounts[0];
// Type-safe interface validates account key internally
let clock = Clock::from_account_info(clock_account)?;
let timestamp = clock.unix_timestamp;
Ok(())
}
Alternative Mitigations
- Anchor Sysvar type: Use
Sysvar<'info, Clock>in account structs for automatic validation.
#[derive(Accounts)]
pub struct CheckTime<'info> {
pub clock: Sysvar<'info, Clock>,
}
- Sysvar::get() (no account needed): For Clock and Rent, you can use
Clock::get()which reads from the runtime directly without requiring an account input.
let clock = Clock::get()?;
let timestamp = clock.unix_timestamp;
- Manual key validation: If you need to read raw sysvar data, validate the key first.
if clock_account.key != &solana_program::sysvar::clock::id() {
return Err(ProgramError::InvalidAccountData);
}
Common Mistakes
- Reading raw bytes instead of using
from_account_info(): Raw byte reads bypass the built-in key validation. Always prefer the type-safe interface. - Validating the account owner instead of the key: Sysvar accounts are owned by the Sysvar program, but checking only the owner is insufficient since an attacker could create another account owned by the Sysvar program (this is not currently possible but relies on implementation details).
- Using
Sysvar::get()in programs that also accept sysvar accounts: If your program accepts a Clock account in its accounts list but usesClock::get()internally, the account is unused and may confuse auditors. Be consistent. - Caching sysvar data across CPIs: Sysvar values (especially Clock) can change between slots. If your instruction spans multiple CPIs, re-read the sysvar after each CPI.