Summary
A Malicious creator can withdraw funds before goal is reached.
Vulnerability Details
There is no check in withdraw for if the goal of the campaign is reached before a creator can withdraw the 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 goal is reached which is against the logic of the program.
Tools Used
Manuel code review
Recommendations
Add check for if goal is reached before withdraws
pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
if ctx.accounts.fund.amount_raised >= ctx.accounts.fund.goal {
return ;
}
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 goal is not reached", async () => {
const creatorBalanceBefore = await provider.connection.getBalance(creator.publicKey);
const fund = await program.account.fund.fetch(fundPDA);
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 goal is not reached (410ms)
We can notice that the creator withdraws funds from the campaign without the 1 sol goal being reached.