Clock Account Spoofing Remediation
How to prevent spoofed Clock sysvar accounts from manipulating time-dependent program logic.
Clock Account Spoofing Remediation
Overview
Related Detector: Clock Account Spoofing
The Clock sysvar is at a fixed Solana address. If a program accepts the clock as a regular AccountInfo without verifying its address, an attacker can pass a fake account containing crafted timestamp data to bypass time-locks, vesting schedules, or auction deadlines.
Recommended Fix
Before (Vulnerable)
pub fn claim_vested(accounts: &[AccountInfo]) -> ProgramResult {
let vesting = &accounts[0];
let clock_account = &accounts[1];
let clock: Clock = bincode::deserialize(&clock_account.data.borrow())?;
if clock.unix_timestamp < vesting.unlock_time {
return Err(ProgramError::Custom(1));
}
Ok(())
}
The program never verifies that clock_account.key is the real Clock sysvar.
After (Fixed)
use solana_program::sysvar::{clock::Clock, Sysvar};
pub fn claim_vested(accounts: &[AccountInfo]) -> ProgramResult {
// Read directly from the runtime — no account lookup needed.
let clock = Clock::get()?;
let vesting = &accounts[0];
if clock.unix_timestamp < vesting.unlock_time {
return Err(ProgramError::Custom(1));
}
Ok(())
}
Clock::get() reads the sysvar from the runtime, eliminating the spoofing surface entirely.
Alternative Mitigations
Verify the sysvar address explicitly
If you must accept the clock as an account (for example, when copying account contexts to a CPI), check the key:
use solana_program::sysvar::clock;
if clock_account.key != &clock::ID {
return Err(ProgramError::InvalidArgument);
}
let clock_data: Clock = bincode::deserialize(&clock_account.data.borrow())?;
Use Anchor’s Sysvar<'info, Clock>
#[derive(Accounts)]
pub struct ClaimVested<'info> {
#[account(mut)]
pub vesting: Account<'info, Vesting>,
pub clock: Sysvar<'info, Clock>,
}
Anchor verifies the address at deserialization. Inside the instruction, prefer Clock::get() since it avoids passing the account at all.
Common Mistakes
Trusting the slot number alone. Slot numbers also come from the Clock sysvar. Spoofing the clock spoofs the slot too — always read both fields from Clock::get().
Caching deserialized clock data across CPIs. Time advances between instructions; refresh the clock at the point of use.
Assuming the runtime always provides the clock. Clock::get() requires the runtime to surface the sysvar. In environments where this fails (highly customized BPF loaders), fall back to address-verified account reads.