Rust Fund

AI First Flight #9
Beginner FriendlyRust
EXP
View results
Submission Details
Impact: medium
Likelihood: high
Invalid

Missing Overflow Check in amount_raised Update

Root + Impact

Description

  • In a crowdfunding campaign, the amount_raised field is intended to keep a cumulative total of all contributions. It is critical that this value remains accurate to correctly determine whether the fundraising goal has been met and to enable other accounting logic. The safe way to update a u64 counter is to use checked arithmetic that prevents overflow.

  • The contribute instruction, however, updates fund.amount_raised using a plain addition without any overflow protection. If amount_raised ever reaches the maximum value u64::MAX and a subsequent contribution would push it beyond this limit, the addition will wrap around to a small number. This corrupts the recorded total and breaks any logic that relies on it.

// Root cause in the codebase with @> marks to highlight the relevant section
pub fn contribute(ctx: Context<FundContribute>, amount: u64) -> Result<()> {
let fund = &mut ctx.accounts.fund;
let contribution = &mut ctx.accounts.contribution;
if fund.deadline != 0 && fund.deadline < Clock::get().unwrap().unix_timestamp.try_into().unwrap() {
return Err(ErrorCode::DeadlineReached.into());
}
// Initialize or update contribution record
if contribution.contributor == Pubkey::default() {
contribution.contributor = ctx.accounts.contributor.key();
contribution.fund = fund.key();
contribution.amount = 0;
}
// Transfer SOL from contributor to fund account
let cpi_context = CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.contributor.to_account_info(),
to: fund.to_account_info(),
},
);
system_program::transfer(cpi_context, amount)?;
// @> Overflow vulnerability: no checked addition
fund.amount_raised += amount;
Ok(())
}

Risk

Likelihood:

  • Low to Medium – For overflow to occur naturally, the total contributions would need to exceed ~1.84 × 10¹⁹ lamports (approximately 18.4 SOL if we consider 1e9 lamports per SOL). While this is a very large number, it is theoretically possible if the campaign accepts a high volume of micro‑donations or if the token has a low decimal count. An attacker could also deliberately aim to cause overflow by making extremely large contributions just before the limit is reached, though this is constrained by the contributor’s own balance and the campaign’s max contribution limits (if any).


Impact:

  • High – Once overflow occurs, the recorded amount_raised becomes a small, incorrect number. This leads to:

    • The contract erroneously reporting that the goal has not been met, even though it actually has, potentially blocking the creator from claiming funds.

    • Contributors being able to refund even after the goal is reached, because the contract believes the goal is still unmet.

    • General accounting chaos that undermines trust in the platform.

Proof of Concept

  1. Assume a campaign where the goal is set to u64::MAX (for illustration). Currently, the amount_raised is u64::MAX - 500.

  2. A user contributes 1000 lamports.

  3. The calculation amount_raised += amount results in:
    (u64::MAX - 500) + 1000 = u64::MAX + 500, which wraps to 499 (since u64 wraps modulo 2^64).

  4. After the transaction, amount_raised is 499, while the actual funds held in the fund account have increased by 1000.

  5. Now any logic checking amount_raised >= goal will be severely broken; the goal may be considered unmet, and refunds may be incorrectly allowed.

Recommended Mitigation

+ add this code
fund.amount_raised = fund.amount_raised
.checked_add(amount)
.ok_or(ErrorCode::Overflow)?;
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 5 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!