RustFund

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

No Deadline Check in Withdraw Function

Summary

The contract allows fund creators to withdraw funds at any time, including before the deadline is reached, which could lead to theft of contributor funds.

Vulnerability Details

The withdraw function has no checks to ensure that the fund's deadline has been reached before allowing the creator to withdraw all funds. This bypasses the core crowdfunding mechanic where funds should only be available to the creator if the funding period has successfully completed.

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
**ctx.accounts.fund.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
**ctx.accounts.creator.to_account_info().try_borrow_mut_lamports()? =
ctx.accounts.creator.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}

Impact

Malicious fund creators can create campaigns, collect contributions, and immediately withdraw all funds before the deadline, effectively stealing from contributors who should have the right to claim refunds if the deadline hasn't been reached.

POC

Add to tests/rustfund.ts:

//audit HIGH - Withdrawal Without Deadline Check
it("Creator can withdraw before deadline", async () => {
const withdrawFundName = "Withdraw Test Fund";
const [withdrawFundPDA] = await PublicKey.findProgramAddress(
[Buffer.from(withdrawFundName), creator.publicKey.toBuffer()],
program.programId
);
await program.methods
.fundCreate(withdrawFundName, description, goal)
.accounts({
fund: withdrawFundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// Set deadline 30 seconds in future
const futureDeadline = new anchor.BN(Math.floor(Date.now() / 1000) + 30);
await program.methods
.setDeadline(futureDeadline)
.accounts({
fund: withdrawFundPDA,
creator: creator.publicKey,
})
.rpc();
// Contribute
const [withdrawContributionPDA] = await PublicKey.findProgramAddress(
[withdrawFundPDA.toBuffer(), provider.wallet.publicKey.toBuffer()],
program.programId
);
await program.methods
.contribute(contribution)
.accounts({
fund: withdrawFundPDA,
contributor: provider.wallet.publicKey,
contribution: withdrawContributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const fundBalanceBefore = await provider.connection.getBalance(withdrawFundPDA);
const creatorBalanceBefore = await provider.connection.getBalance(creator.publicKey);
// Withdraw before deadline
await program.methods
.withdraw()
.accounts({
fund: withdrawFundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const fundBalanceAfter = await provider.connection.getBalance(withdrawFundPDA);
const creatorBalanceAfter = await provider.connection.getBalance(creator.publicKey);
console.log(`Fund balance before: ${fundBalanceBefore}, after: ${fundBalanceAfter}`);
console.log(`Creator balance increased by: ${(creatorBalanceAfter - creatorBalanceBefore)/1000000000} SOL`);
});```

Output:

========================================
🐛 BUG REPORT [HIGH]: No Deadline Check in Withdraw Function
----------------------------------------
Description: Creator can withdraw funds before the deadline, potentially stealing contributors' funds that should be refundable
Evidence: Successfully withdrew funds before deadline. Fund balance before: 537590960, after: 37590960. Creator balance increased by ~0.499995008 SOL
========================================

Tools Used

  • Anchor framework for testing

  • Manual code review

Recommendations

Add a deadline check to the withdraw function:

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
// Check if deadline has passed
+ if ctx.accounts.fund.deadline == 0 || ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineNotReached.into());
}
let amount = ctx.accounts.fund.amount_raised;
// Rest of the function remains the same
...
}
Updates

Appeal created

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

No deadline check in `withdraw` function

Support

FAQs

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