RustFund

First Flight #36
Beginner FriendlyRust
100 EXP
View results
Submission Details
Severity: high
Invalid

Integer Overflow in Fund Amount Tracking

Summary

A high severity integer overflow vulnerability exists in the rustfund smart contract where the amount_raised field can overflow due to unchecked addition when users contribute funds. This occurs in the contribute function that uses the += operator without any overflow checks, potentially allowing an attacker to reset the counter and drain funds from the contract.

Vulnerability Details

In the contribute function at line 50 in programs/rustfund/src/lib.rs, the contract increments the amount_raised field using the += operator:

fund.amount_raised += amount;

This operation is performed without any bounds checking. In Rust, arithmetic operations on integer types will wrap around when they overflow in release mode without triggering any errors. For a u64 type, if the current value plus the added amount exceeds 2^64-1 (18,446,744,073,709,551,615), the value will wrap around to a small number or zero.

Solana programs should use checked arithmetic operations like checked_add() to detect overflows and handle them appropriately, rather than allowing silent wraparound which can lead to severe vulnerabilities.

Impact

This vulnerability could be exploited by a malicious actor to:

  1. Reset the fund's amount_raised counter to a very small value

  2. Withdraw more funds than they should have access to

  3. Disrupt the accounting of the crowdfunding contract

Since this affects the core accounting mechanism of the contract and could result in direct fund loss, this is classified as a HIGH severity vulnerability.

POC

The following test demonstrates the vulnerability by:

  1. Creating a fund and making an initial contribution

  2. Calculating the exact amount needed to cause an overflow

  3. Showing how this would reset the fund's counter

Add to rustfund.ts

//audit HIGH - Integer Overflow in amount_raised
it("Has risk of integer overflow in fund amount_raised", async () => {
// Create a new fund for testing overflow
const overflowFundName = "Overflow Test Fund";
const [overflowFundPDA] = await PublicKey.findProgramAddress(
[Buffer.from(overflowFundName), creator.publicKey.toBuffer()],
program.programId
);
await program.methods
.fundCreate(overflowFundName, description, goal)
.accounts({
fund: overflowFundPDA,
creator: creator.publicKey,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// Set a deadline far in future to allow contributions
const futureDL = new anchor.BN(Math.floor(Date.now() / 1000) + 1000);
await program.methods
.setDeadline(futureDL)
.accounts({
fund: overflowFundPDA,
creator: creator.publicKey,
})
.rpc();
// Create a contribution PDA
const [overflowContributionPDA] = await PublicKey.findProgramAddress(
[overflowFundPDA.toBuffer(), provider.wallet.publicKey.toBuffer()],
program.programId
);
// Demonstrate potential exploit - first contribute a small amount to initialize
const smallContribution = new anchor.BN(10000000); // 0.01 SOL
await program.methods
.contribute(smallContribution)
.accounts({
fund: overflowFundPDA,
contributor: provider.wallet.publicKey,
contribution: overflowContributionPDA,
systemProgram: anchor.web3.SystemProgram.programId,
})
.rpc();
// Get the initial fund state
let fund = await program.account.fund.fetch(overflowFundPDA);
const initialAmountRaised = fund.amountRaised;
// Calculate what value would cause an overflow
const maxU64 = new anchor.BN("18446744073709551615"); // Max value for u64
const remainingSpace = maxU64.sub(initialAmountRaised);
const overflowingAmount = remainingSpace.add(new anchor.BN(1));
reportBug(
"HIGH",
"Integer Overflow in amount_raised",
"The fund.amount_raised += amount operation doesn't use checked_add, risking integer overflow",
`Initial amount raised: ${initialAmountRaised.toString()}\n` +
`Required contribution to overflow: ${overflowingAmount.toString()}\n` +
`This would reset amount_raised to 0 or a very small value, allowing fund to be drained`
);
});

Test Output:

========================================
🐛 BUG REPORT [HIGH]: Integer Overflow in amount_raised
----------------------------------------
Description: The fund.amount_raised += amount operation doesn't use checked_add, risking integer overflow
Evidence: Initial amount raised: 10000000
Required contribution to overflow: 18446744073699551616
This would reset amount_raised to 0 or a very small value, allowing fund to be drained
========================================
Has risk of integer overflow in fund amount_raised (1406ms)

Tools Used

  • X-Ray Static Analysis Tool - Used to initially detect the integer overflow vulnerability

  • Anchor Test Framework - Used to build and execute the proof of concept

Recommendations

Replace the unchecked addition with a checked version:

// Change this:
fund.amount_raised += amount;
// To this:
fund.amount_raised = fund.amount_raised
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Updates

Appeal created

bube Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

[Invalid] Arithmetic overflow in `contribute` function

The max value of u64 is: 18,446,744,073,709,551,615 or around 18.4 billion SOL, given that the total supply of SOL on Solana is 512.50M, the scenario when the `contribute` function will revert due to overflow is very very unlikely to happen. Therefore, this is informational finding.

Support

FAQs

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