Skip to main content
Sigvex

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:

  1. A custom token program tracks balances as u64.
  2. A withdraw instruction computes balance -= amount without checking balance >= amount.
  3. In release mode, if balance == 0 and amount == 1, the result is u64::MAX (18.4 × 10^18).
  4. The attacker’s balance is now astronomically large.
  5. The attacker withdraws up to u64::MAX tokens from the protocol.

Supply inflation via addition:

  1. A minting function computes total_supply += mint_amount without overflow protection.
  2. An attacker mints an amount that causes total_supply to wrap around to a small number.
  3. The actual tokens in circulation is u64::MAX but the recorded supply is near-zero.
  4. The total_supply check (e.g., cap enforcement) is bypassed.

Price manipulation via multiplication:

  1. A price computation: price = token_amount * price_per_token where both are u64.
  2. If token_amount = 10^10 and price_per_token = 10^12, the product overflows u64.
  3. The wrapped result is used as the collateral value.
  4. The attacker provides a large token amount that produces a wrapped-around small price.
  5. 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

Apply checked arithmetic consistently across all numeric operations in Solana programs:

  1. Use checked methods: checked_add, checked_sub, checked_mul, checked_div — all return Option<T>, returning None instead of overflowing.

  2. Use saturating methods when appropriate: saturating_add, saturating_sub — these clamp at the type’s min/max rather than wrapping.

  3. 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.

  4. Consider the safe-math or checked-math crates: These provide macros that automatically add overflow checks to arithmetic operations.

  5. 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))?
    };
}

References