Rust Fund

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

Unchecked arithmetic on amount_raised allows integer overflow

Root + Impact

Description

  • High contribution volume or massive deposits pushing the total amount raised beyond the limits of u64::MAX.


#[test]
#[should_panic(expected = "attempt to add with overflow")]
fn test_amount_raised_overflow_vulnerability() {
setup_syscall_stubs();
let fund_key = Pubkey::new_unique();
let contributor_key = Pubkey::new_unique();
let contribution_key = Pubkey::new_unique();
let program_id = crate::ID;
let mut fund_lamports = 100_000_000;
let mut fund_data = vec![0u8; 8 + Fund::INIT_SPACE];
let fund_state = Fund {
name: "Overflow Campaign".to_string(),
description: "Desc".to_string(),
goal: 500,
deadline: 0,
creator: Pubkey::new_unique(),
amount_raised: u64::MAX, // Set to max value
dealine_set: false,
};
let mut writer = &mut fund_data[..];
fund_state.try_serialize(&mut writer).unwrap();
let mut contributor_lamports = 1_000_000_000;
let mut contributor_data = vec![];
let mut contribution_lamports = 10_000_000;
let mut contribution_data = vec![0u8; 8 + Contribution::INIT_SPACE];
contribution_data[..8].copy_from_slice(&Contribution::DISCRIMINATOR);
let system_program_key = anchor_lang::solana_program::system_program::ID;
let bpf_loader_id = anchor_lang::solana_program::bpf_loader::id();
let mut system_program_lamports = 0;
let mut system_program_data = vec![];
let system_program_info = AccountInfo::new(
&system_program_key, false, false, &mut system_program_lamports, &mut system_program_data, &bpf_loader_id, true, 0
);
let fund_info = AccountInfo::new(&fund_key, false, true, &mut fund_lamports, &mut fund_data, &program_id, false, 0);
let contributor_info = AccountInfo::new(&contributor_key, true, true, &mut contributor_lamports, &mut contributor_data, &program_id, false, 0);
let contribution_info = AccountInfo::new(&contribution_key, false, true, &mut contribution_lamports, &mut contribution_data, &program_id, false, 0);
let fund_acc: Account<Fund> = Account::try_from(&fund_info).unwrap();
let contributor_sig: Signer = Signer::try_from(&contributor_info).unwrap();
let contribution_acc: Account<Contribution> = Account::try_from(&contribution_info).unwrap();
let system_prog: Program<System> = Program::try_from(&system_program_info).unwrap();
let mut accounts = FundContribute {
fund: fund_acc,
contributor: contributor_sig,
contribution: contribution_acc,
system_program: system_prog,
};
let ctx = Context::new(&program_id, &mut accounts, &[], FundContributeBumps::default());
// Act: contribute 1 lamport, which triggers integer overflow on amount_raised
let _ = crate::rustfund::contribute(ctx, 1);
}


POC Explanition:


The test initializes a mock Fund state with its amount_raised field set to u64::MAX. It then calls the contribute instruction to add 1 lamport to the campaign. Because the addition uses unchecked logic (+=), Rust panics with an overflow exception, indicating that the program fails to handle large values safely.
Risk

Likelihood:

  • In debug profiles, the program will panic and refuse contributions.


Impact:

  • In debug profiles, the program will panic and refuse contributions.

  • In release profiles, the amount wraps around, resulting in a corrupted state where the goal is falsely marked as incomplete and withdrawals fail.

Proof of Concept

#[test]
#[should_panic(expected = "attempt to add with overflow")]
fn test_amount_raised_overflow_vulnerability() {
setup_syscall_stubs();
let fund_key = Pubkey::new_unique();
let contributor_key = Pubkey::new_unique();
let contribution_key = Pubkey::new_unique();
let program_id = crate::ID;
let mut fund_lamports = 100_000_000;
let mut fund_data = vec![0u8; 8 + Fund::INIT_SPACE];
let fund_state = Fund {
name: "Overflow Campaign".to_string(),
description: "Desc".to_string(),
goal: 500,
deadline: 0,
creator: Pubkey::new_unique(),
amount_raised: u64::MAX, // Set to max value
dealine_set: false,
};
let mut writer = &mut fund_data[..];
fund_state.try_serialize(&mut writer).unwrap();
let mut contributor_lamports = 1_000_000_000;
let mut contributor_data = vec![];
let mut contribution_lamports = 10_000_000;
let mut contribution_data = vec![0u8; 8 + Contribution::INIT_SPACE];
contribution_data[..8].copy_from_slice(&Contribution::DISCRIMINATOR);
let system_program_key = anchor_lang::solana_program::system_program::ID;
let bpf_loader_id = anchor_lang::solana_program::bpf_loader::id();
let mut system_program_lamports = 0;
let mut system_program_data = vec![];
let system_program_info = AccountInfo::new(
&system_program_key, false, false, &mut system_program_lamports, &mut system_program_data, &bpf_loader_id, true, 0
);
let fund_info = AccountInfo::new(&fund_key, false, true, &mut fund_lamports, &mut fund_data, &program_id, false, 0);
let contributor_info = AccountInfo::new(&contributor_key, true, true, &mut contributor_lamports, &mut contributor_data, &program_id, false, 0);
let contribution_info = AccountInfo::new(&contribution_key, false, true, &mut contribution_lamports, &mut contribution_data, &program_id, false, 0);
let fund_acc: Account<Fund> = Account::try_from(&fund_info).unwrap();
let contributor_sig: Signer = Signer::try_from(&contributor_info).unwrap();
let contribution_acc: Account<Contribution> = Account::try_from(&contribution_info).unwrap();
let system_prog: Program<System> = Program::try_from(&system_program_info).unwrap();
let mut accounts = FundContribute {
fund: fund_acc,
contributor: contributor_sig,
contribution: contribution_acc,
system_program: system_prog,
};
let ctx = Context::new(&program_id, &mut accounts, &[], FundContributeBumps::default());
// Act: contribute 1 lamport, which triggers integer overflow on amount_raised
let _ = crate::rustfund::contribute(ctx, 1);
}

Recommended Mitigation

system_program::transfer(cpi_context, amount)?;
- fund.amount_raised += amount;
+ fund.amount_raised = fund.amount_raised
+ .checked_add(amount)
+ .ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!