Arithmetic Overflow Exploit Generator
Exploit generator that validates unchecked arithmetic vulnerabilities in Solana programs where integer overflow or underflow in token balance calculations allows fund theft or state corruption.
Arithmetic Overflow Exploit Generator
Overview
The arithmetic overflow exploit generator validates findings from the unchecked-arithmetic detector by classifying arithmetic vulnerabilities in Solana programs. Unlike EVM programs (where Solidity 0.8+ has built-in overflow protection), Solana programs written in Rust use u64 arithmetic that panics on overflow in debug mode but wraps silently in release mode. This class of vulnerability is assessed via static analysis of arithmetic operation patterns in the program bytecode.
Solana token amounts are represented as u64 (0 to 18,446,744,073,709,551,615). In Rust’s release mode, u64 arithmetic wraps on overflow: u64::MAX + 1 == 0. A program that computes balance += amount without overflow protection can wrap a near-maximum balance to zero, or wrap a zero balance to near-maximum. The same wrapping occurs for subtraction: 0u64 - 1 == u64::MAX. Token programs that allow balance manipulation through arithmetic wrapping can be drained or inflated.
Severity score: 70/100 (High).
Note: Exploit generation in Sigvex is for vulnerability validation purposes only.
Attack Scenario
Balance underflow via subtraction:
- A custom token program tracks balances as
u64. - A
withdrawinstruction computesbalance -= amountwithout checkingbalance >= amount. - In release mode, if
balance == 0andamount == 1, the result isu64::MAX(18.4 × 10^18). - The attacker’s balance is now astronomically large.
- The attacker withdraws up to
u64::MAXtokens from the protocol.
Supply inflation via addition:
- A minting function computes
total_supply += mint_amountwithout overflow protection. - An attacker mints an amount that causes
total_supplyto wrap around to a small number. - The actual tokens in circulation is
u64::MAXbut the recorded supply is near-zero. - The total_supply check (e.g., cap enforcement) is bypassed.
Price manipulation via multiplication:
- A price computation:
price = token_amount * price_per_tokenwhere both areu64. - If
token_amount = 10^10andprice_per_token = 10^12, the product overflows u64. - The wrapped result is used as the collateral value.
- The attacker provides a large token amount that produces a wrapped-around small price.
- The attacker appears to have low collateral value and triggers favorable liquidations.
Exploit Mechanics
The engine maps unchecked-arithmetic detector findings to the arithmetic overflow exploit class. Assessment is performed through static analysis of the program’s arithmetic operations rather than dynamic simulation, since arithmetic wrapping does not produce a runtime failure in release mode — it silently produces an incorrect result that must be detected by examining the program logic.
Key overflow thresholds:
u64::MAX = 18_446_744_073_709_551_615(approximately 1.8 × 10^19)- SOL token units: 1 SOL = 10^9 lamports; overflow at ~18.4 × 10^9 SOL
- SPL token amounts: depends on decimals (6 decimals → overflow at ~18.4 trillion tokens)
// VULNERABLE: Unchecked arithmetic in release mode
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> ProgramResult {
let user_account = &mut ctx.accounts.user_token_account;
// VULNERABLE: In release mode, 0 - 1 = u64::MAX
user_account.amount -= amount; // Wraps on underflow!
// Transfer amount to user
token::transfer(ctx.accounts.transfer_ctx(), amount)?;
Ok(())
}
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> ProgramResult {
let pool = &mut ctx.accounts.pool;
// VULNERABLE: u64::MAX + 1 = 0 (supply counter wraps to zero)
pool.total_deposits += amount; // Wraps on overflow!
Ok(())
}
// SECURE: Use checked arithmetic everywhere
pub fn withdraw_safe(ctx: Context<Withdraw>, amount: u64) -> ProgramResult {
let user_account = &mut ctx.accounts.user_token_account;
// checked_sub returns None on underflow
user_account.amount = user_account.amount
.checked_sub(amount)
.ok_or(TokenError::InsufficientFunds)?;
token::transfer(ctx.accounts.transfer_ctx(), amount)?;
Ok(())
}
pub fn deposit_safe(ctx: Context<Deposit>, amount: u64) -> ProgramResult {
let pool = &mut ctx.accounts.pool;
// checked_add returns None on overflow
pool.total_deposits = pool.total_deposits
.checked_add(amount)
.ok_or(TokenError::Overflow)?;
Ok(())
}
// Safe multiplication for price calculations
fn compute_price(token_amount: u64, price_per_token: u64) -> Result<u64> {
token_amount
.checked_mul(price_per_token)
.ok_or(error!(ErrorCode::MathOverflow))
}
// Use u128 for intermediate calculations that might exceed u64
fn compute_price_u128(token_amount: u64, price_per_token: u64) -> Result<u64> {
let result = (token_amount as u128)
.checked_mul(price_per_token as u128)
.ok_or(error!(ErrorCode::MathOverflow))?;
// Check the result fits in u64
u64::try_from(result).map_err(|_| error!(ErrorCode::MathOverflow))
}
Remediation
- Detector: Arithmetic Overflow Detector
- Remediation Guide: Arithmetic Overflow Remediation
Apply checked arithmetic consistently across all numeric operations in Solana programs:
-
Use checked methods:
checked_add,checked_sub,checked_mul,checked_div— all returnOption<T>, returningNoneinstead of overflowing. -
Use saturating methods when appropriate:
saturating_add,saturating_sub— these clamp at the type’s min/max rather than wrapping. -
Use u128 for intermediate products: When multiplying two u64 values, the product can exceed u64::MAX. Perform the multiplication in u128, then convert back to u64 with bounds checking.
-
Consider the
safe-mathorchecked-mathcrates: These provide macros that automatically add overflow checks to arithmetic operations. -
Test with boundary values: Always fuzz-test token amount calculations with
u64::MAX,u64::MAX - 1, 0, and 1. These edge cases reveal most overflow vulnerabilities.
// Recommended: Define a macro or helper for all math operations
macro_rules! safe_add {
($a:expr, $b:expr) => {
$a.checked_add($b).ok_or(error!(ErrorCode::MathOverflow))?
};
}