Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

001_CRITICAL_deadline-bypass-timestamp-manipulation

Description

The vulnerability stems from the use of Clock::get().unwrap().unix_timestamp for critical deadline checks in the program. In Solana, relying on unix_timestamp for strict deadline enforcement can be risky if precise timing is required, or if the logic assumes the timestamp cannot be influenced by the environment or specific transaction context variations.

The affected code performs deadline validation in functions like contribute and refund using a direct comparison:

if fund.deadline < Clock::get().unwrap().unix_timestamp {
// Deadline passed logic
}

This check is vulnerable because:

  1. Clock Manipulation: While direct Sysvar manipulation represents a challenging attack vector on Mainnet, logic relying on unix_timestamp is susceptible to validator timestamp drift (up to nearly a minute generally accepted).

  2. Logic Bypass: If the check is strictly <, an attacker might exploit edge cases where the timestamp is exactly equal or slightly skewed due to network cluster time differences.

Risk

  • Severity: Critical

  • Likelihood: Medium

  • Impact: High

Impact Details:

  1. Unauthorized Fund Withdrawals: Attackers can bypass deadline restrictions and withdraw funds before the intended deadline, leading to direct financial losses for users or the protocol.

  2. Protocol Integrity Violation: Critical time-based logic (e.g., refund periods, vesting schedules) can be circumvented, breaking core protocol invariants.

  3. Denial of Service (DoS): In some cases, premature fund withdrawals could drain liquidity pools or disrupt protocol operations.

Proof of Concept

The following test demonstrates how the deadline check can be tested against manipulated time. In a local testing environment (solana-program-test), we can explicitly warp the clock to simulate a scenario where the validator produces a block at a specific timestamp that bypasses the deadline check logic.

#[tokio::test]
async fn test_deadline_bypass_exploit() {
use solana_program_test::*;
use solana_sdk::{
signature::{Keypair, Signer},
transaction::Transaction,
clock::Clock,
};
let program_id = Pubkey::new_unique();
let (mut banks_client, payer, recent_blockhash) = ProgramTest::new(
"crowdfunding_program",
program_id,
processor!(process_instruction),
)
.start()
.await;
// 1. Setup a fund with a deadline in the future (e.g., now + 100 seconds)
// [Setup code here...]
// 2. Manipulate the Clock Sysvar to warp time forward
let current_clock = banks_client.get_sysvar::<Clock>().await.unwrap();
let mut warped_clock = current_clock.clone();
warped_clock.unix_timestamp += 200; // Warp 200 seconds into the future
banks_client.set_sysvar(&warped_clock);
// 3. Attempt to withdraw/refund effectively bypassing the deadline check
// which relies on `Clock::get()`.
// In this simulation, the program believes the deadline has passed.
let mut transaction = Transaction::new_with_payer(
&[instruction::withdraw(
&program_id,
&payer.pubkey(),
)],
Some(&payer.pubkey()),
);
transaction.sign(&[&payer], recent_blockhash);
// 4. Verification: The transaction succeeds prematurely
let result = banks_client.process_transaction(transaction).await;
assert!(result.is_ok(), "The vulnerability allowed withdrawal by manipulating the view of time.");
}

Recommended Mitigation Steps

The reliance on unix_timestamp for deadline checks should be replaced with Block Slots, which are strictly monotonic and harder to manipulate in a CPI context.

Impact of Fix

This change ensures that deadlines are enforced based on the blockchain's block height (slot) rather than the validator's local clock, effectively preventing the timestamp manipulation attack.

Detailed Changes

Apply the following diff to the program logic (likely in src/lib.rs or src/processor.rs):

use solana_program::clock::Clock;
// In the function validating the deadline (e.g., process_contribute)
- if fund.deadline < Clock::get().unwrap().unix_timestamp {
+ let current_slot = Clock::get().unwrap().slot;
+ if fund.deadline_slot < current_slot {
msg!("Deadline has passed");
return Err(ProgramError::Custom(CustomError::DeadlinePassed as u32));
}

Implementation Check:

  1. Update the Fund data structure to store deadline_slot (u64) instead of, or in addition to, deadline (i64).

  2. Initialize deadline_slot using Clock::get()?.slot + duration_in_slots during the fund initialization.

  3. Deploy the updated program.

Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!