Skip to main content
Sigvex

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.

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 the account-validation framework’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.

References