Skip to main content
Sigvex

Integer Overflow Remediation (SVM)

How to prevent integer overflow and underflow in Solana programs by using checked arithmetic methods and the account-validation framework checked_math! macro.

Integer Overflow Remediation (SVM)

Overview

Rust release builds use wrapping arithmetic by default — overflow silently wraps to zero or maximum rather than panicking. Solana programs compiled in release mode are therefore vulnerable to integer overflow on user-controlled values unless checked arithmetic is used explicitly. The remediation is to replace all arithmetic on user-controlled financial values with checked_add, checked_sub, checked_mul, or the account-validation framework’s checked_math! macro.

Related Detector: Integer Overflow (SVM)

Before (Vulnerable)

// Wrapping arithmetic in release mode — silent overflow
pub fn deposit(vault: &mut Vault, amount: u64) -> ProgramResult {
    vault.balance = vault.balance + amount; // Silently wraps in release
    Ok(())
}

After (Fixed)

use solana_program::program_error::ProgramError;

// Checked arithmetic — returns error on overflow
pub fn deposit(vault: &mut Vault, amount: u64) -> ProgramResult {
    vault.balance = vault.balance
        .checked_add(amount)
        .ok_or(ProgramError::ArithmeticOverflow)?;
    Ok(())
}

Alternative Mitigations

Anchor checked_math! macro — wraps multiple arithmetic operations in a single checked block:

use anchor_lang::prelude::*;

pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> {
    let vault = &mut ctx.accounts.vault;

    // checked_math! wraps all enclosed operations — any overflow returns Err
    vault.balance = checked_math!(vault.balance + amount)?;

    // Also works for complex expressions
    let fee = checked_math!(amount * FEE_RATE / FEE_BASIS)?;
    vault.accumulated_fees = checked_math!(vault.accumulated_fees + fee)?;

    Ok(())
}

Saturating arithmetic — for counters and reputation scores where clamping to maximum is acceptable semantics:

// Only for non-financial values where clamping is acceptable
score = score.saturating_add(points); // Clamps to u64::MAX rather than wrapping

Explicit bounds check — for cases where the business logic requires rejecting out-of-range values:

const MAX_DEPOSIT: u64 = 1_000_000_000; // 1B lamports

pub fn deposit(vault: &mut Vault, amount: u64) -> ProgramResult {
    require!(amount <= MAX_DEPOSIT, MyError::DepositTooLarge);
    vault.balance = vault.balance
        .checked_add(amount)
        .ok_or(ProgramError::ArithmeticOverflow)?;
    Ok(())
}

Common Mistakes

Using + or - in release builds — Rust’s + operator wraps in release mode. Always use checked_add / checked_sub for financial values.

Mixing checked and unchecked in the same function — if one operation is unchecked, overflow can still propagate into subsequent checked operations as a normal value.

saturating_add for financial balances — saturating at u64::MAX is not a valid outcome for a token balance. Use checked arithmetic and return an error instead.

References