Rust Fund

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

set_deadline() accepts past timestamps — creator can immediately expire a campaign

Root + Impact

Description

set_deadline() writes the caller-provided deadline directly to fund.deadline without verifying it is in the future. A creator who passes deadline = 1 (Unix epoch) immediately places the campaign in an expired state: contribute() rejects all new deposits and refund() unlocks for all contributors. This can be used as a malicious emergency exit combined with S-03 — withdraw first, then set a past deadline to prevent any further analysis of the campaign's state.

pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.dealine_set {
return Err(ErrorCode::DeadlineAlreadySet.into());
}
@> // BUG: No require!(deadline > Clock::get()?.unix_timestamp as u64)
fund.deadline = deadline;
Ok(())
}

Risk

Likelihood:

  • Client-side timestamp bugs (passing milliseconds instead of seconds, off-by-one errors, timezone mishandling) commonly produce past timestamps — accidental expiry is a realistic outcome for non-malicious creators

  • A malicious creator can deliberately set deadline = 1 to immediately trigger campaign expiry, blocking further contributions

Impact:

  • Setting a past deadline immediately halts all new contributions to an active campaign, potentially cutting short a successful fundraise at any point

  • All existing contributors can immediately claim refunds, collapsing the campaign even when it was progressing toward its goal

Proof of Concept

it('past deadline immediately expires active campaign', async () => {
// Active campaign with a live contribution
await contributeSOL(contributor, 1_000_000_000);
@>// Creator sets deadline to Unix epoch (Jan 1 1970) — far in the past
await program.methods.setDeadline(new BN(1))
.accounts({ fund: fundPda, creator: creator.publicKey })
.signers([creator]).rpc();
// New contributions immediately rejected
await assert.rejects(
program.methods.contribute(new BN(500_000_000))
.accounts({...}).signers([contributor2]).rpc(),
/DeadlineReached/
);
// Existing contributors can refund immediately
await program.methods.refund()
.accounts({...}).signers([contributor]).rpc();
// Campaign collapsed instantly
});

Recommended Mitigation

pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: i64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
+ let clock = Clock::get()?;
+ require!(
+ deadline > clock.unix_timestamp,
+ ErrorCode::InvalidDeadline
+ );
if fund.dealine_set {
return Err(ErrorCode::DeadlineAlreadySet.into());
}
fund.deadline = deadline;
+ fund.dealine_set = true;
Ok(())
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!