Ожидаемое поведение: Протокол краудфандинга должен позволять создателю фонда вывести собранные средства (amount_raised) на свой кошелек. При этом подразумевается, что средства заблокированы до наступления дедлайна (fund.deadline), чтобы пользователи имели временное окно для возврата своих взносов (refund), если проект не состоялся.
Конкретная проблема: В функции withdraw полностью отсутствует проверка временного ограничения (fund.deadline). Создатель может вызвать функцию вывода средств в любой момент времени, даже до наступления дедлайна. Это позволяет создателю мгновенно опустошить PDA-аккаунт фонда, что приведет к тому, что вкладчики физически не смогут вызвать refund (транзакция будет отклонена из-за нехватки lamports на контракте).
Likelihood: High
Reason 1: Создатель фонда имеет прямой контроль над вызовом функции withdraw и экономически мотивирован вывести средства как можно скорее, чтобы избежать возврата средств пользователям в случае отмены проекта.
Reason 2: Для эксплуатации не требуется выполнения сложных условий или взлома криптографии. Вектор активируется одной транзакцией от создателя в любой момент после того, как на балансе PDA фонда скопятся lamports от вкладчиков.
Impact: High
Impact 1: Полная и безвозвратная потеря средств всеми вкладчиками. После вывода средств создателем, баланс PDA фонда (fund.to_account_info().lamports) становится равен нулю (или опускается ниже rent-exempt минимума), из-за чего операция checked_sub(amount) в функции refund всегда будет возвращать ошибку ProgramError::InsufficientFunds.
Impact 2: Нарушается базовая логика и доверие к протоколу краудфандинга. Протокол перестает выполнять свою основную функцию — удержание средств до дедлайна (escrow), превращаясь в прямую передачу средств создателю без гарантий возврата.
Сводка:
Файл: programs\rustfund\src\lib.rs (функция withdraw и структура FundWithdraw)
Likelihood: High
Impact: High
## Description 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. ```Rust 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. ## Recommendations Add check for if campaign as reached deadline before a creator can withdraw ```Rust pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> { //add this if ctx.accounts.fund.deadline != 0 && ctx.accounts.fund.deadline > Clock::get().unwrap().unix_timestamp.try_into().unwrap() { return Err(ErrorCode::DeadlineNotReached.into()); } //stops here 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: ```TypeScript 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)); //default 15000 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: ```Python 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.
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.