RustFund

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

Multiple Withdrawals Possible

Summary

This report highlights a vulnerability in the withdraw function that allows campaign creators to withdraw funds multiple times without updating the contract’s state. This flaw enables infinite withdrawals, potentially draining all funds from the contract and causing state inconsistencies that could affect refunds and accounting.

Vulnerability Details

Affected Function: withdraw

Code Snippet:

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let amount = ctx.accounts.fund.amount_raised;
// Transfers `amount` SOL to the creator
// ❌ Missing: Reset `amount_raised` or mark the fund as closed
}

Issue:

  • The function does not reset amount_raised or mark the campaign as closed.

  • Since the same amount remains recorded in amount_raisedThe creator can call withdraw multiple times, continuously draining funds.

  • The function does not verify the actual SOL balance of the campaign account, leading to potential over-withdrawals if SOL is sent directly to the fund.

Impact:

Direct Exploits

  1. Infinite Withdrawals:

    • The contract does not track whether funds have already been withdrawn.

    • The creator can repeatedly withdraw the same amount, exceeding the campaign’s actual balance.

  2. Overdrawn Campaign Funds:

    • If external SOL transfers increase the campaign’s balance, withdrawals may exceed the intended limit.

    • Example Exploit: A donor sends SOL directly to the contract account → withdraw allows the creator to drain this extra SOL as well.

  3. Incorrect Refunds:

    • Since amount_raised is never updated, contributors may attempt to claim refunds for non-existent funds, leading to errors or failed transactions.

Tools Used

  • Manual Code Review: Identified the missing state updates and incorrect fund tracking.

Recommendations

1. Verify and Use Actual Balance for Withdrawals

Ensure that withdrawals do not** exceed** the fund’s actual SOL balance.

Fixed Code:

let fund_balance = ctx.accounts.fund.to_account_info().lamports();
let amount = fund_balance.min(fund.amount_raised); // Use actual balance

2. Mark Campaign as Closed After Withdrawal

Introduce a closed flag to prevent multiple withdrawals.

Updated Struct:

#[account]
pub struct Fund {
pub closed: bool, // ✅ New flag to track withdrawal state
}

Updated Function:

pub fn withdraw(ctx: Context<FundWithdraw>) -> Result<()> {
let fund = &mut ctx.accounts.fund;
require!(!fund.closed, ErrorCode::AlreadyClosed); // Prevent multiple withdrawals
let amount = fund.amount_raised;
fund.amount_raised = 0; // ✅ Reset amount_raised after withdrawal
fund.closed = true; // ✅ Mark fund as closed
// Proceed with transfer...
}

3. Implement Explicit AlreadyClosed Error Code

#[error_code]
pub enum ErrorCode {
#[msg("Funds have already been withdrawn from this campaign")]
AlreadyClosed,
}

Conclusion

The current implementation fails to update the state after withdrawal, allowing creators to drain funds repeatedly. By incorporating state tracking (via closed flag) and ensuring withdrawals do not exceed actual balances, this vulnerability can be effectively mitigated.

Updates

Appeal created

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

`amount_raised` is not reset to 0 in `withdraw` function

Support

FAQs

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