Summary
A Malicious creator can withdraw funds before the campaign's deadline.
Vulnerability Details
There is no check in withdraw if the campaign ended before the creator can withdraw funds.
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
A Malicious creator can withdraw all the campaign funds before deadline which is against the intended logic of the program.
Tools Used
Manuel code review
Recommendations
Add check for if campaign as reached deadline before a creator can withdraw
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
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;
**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(())
}
POC
keep everything in ./tests/rustfund.rs up on to Contribute to fund test, then add the below:
it("Creator withdraws funds when deadline is not reached", async () => {
const creatorBalanceBefore = await provider.connection.getBalance(creator.publicKey);
const fund = await program.account.fund.fetch(fundPDA);
await new Promise(resolve => setTimeout(resolve, 150));
console.log("goal", fund.goal.toNumber());
console.log("fundBalance", await provider.connection.getBalance(fundPDA));
console.log("creatorBalanceBefore", await provider.connection.getBalance(creator.publicKey));
await program.methods
.withdraw()
.accounts({
fund: fundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
const creatorBalanceAfter = await provider.connection.getBalance(creator.publicKey);
console.log("creatorBalanceAfter", creatorBalanceAfter);
console.log("fundBalanceAfter", await provider.connection.getBalance(fundPDA));
});
this outputs:
goal 1000000000
fundBalance 537590960
creatorBalanceBefore 499999999460946370
creatorBalanceAfter 499999999960941400
fundBalanceAfter 37590960
✔ Creator withdraws funds when deadline is not reached (398ms)
We can notice that the creator withdraws funds from the campaign before the deadline.