Skip to main content
Sigvex

Integer Overflow (SVM)

Detects unchecked arithmetic operations on user-controlled values in Solana programs that may overflow or underflow, enabling fund manipulation.

Integer Overflow (SVM)

Overview

Remediation Guide: How to Fix Integer Overflow (SVM)

The SVM integer overflow detector identifies arithmetic operations on user-controlled values in Solana programs where overflow or underflow can occur without producing an error. Rust’s default release-build semantics perform wrapping arithmetic — no panic on overflow. Solana programs compiled in release mode therefore silently wrap u64::MAX + 1 to 0 unless the developer explicitly uses checked (checked_add, checked_sub) or saturating variants.

The detector performs taint-propagation analysis: all function parameters and account data reads (Load) are treated as user-controlled. Any arithmetic operation (Add, Sub, Mul) on tainted operands that is not protected by a checked variant is flagged. Loop counters are filtered to reduce false positives. Confidence is adjusted downward for Anchor programs (which conventionally use checked_math!) and read-only functions.

Why This Is an Issue

Rust’s release build wrapping arithmetic is a deliberate design choice for performance, but it means financial logic that operates on user-supplied amounts is vulnerable without explicit protection. An attacker who supplies a crafted amount close to u64::MAX can cause the sum of a balance and the supplied amount to wrap to a small value, bypassing balance checks or minting unlimited tokens.

Unlike Solidity’s pre-0.8 overflow, Rust wrapping arithmetic does not panic — the program completes successfully with a corrupted value. This makes the attack invisible in logs: the transaction succeeds and the resulting state appears valid until the corrupted value is used.

How to Resolve

Replace all arithmetic on user-controlled financial values with checked variants. Return an error when overflow is detected. Use Anchor’s checked_math! macro to wrap multiple arithmetic operations in a single checked block.

// Before: Vulnerable — raw arithmetic on user-controlled amount
pub fn deposit(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let mut data = accounts[0].data.borrow_mut();
    let existing_balance = u64::from_le_bytes(data[0..8].try_into().unwrap());
    let new_balance = existing_balance + amount; // Wraps silently in release mode
    data[0..8].copy_from_slice(&new_balance.to_le_bytes());
    Ok(())
}

// After: Fixed — checked arithmetic
pub fn deposit(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let mut data = accounts[0].data.borrow_mut();
    let existing_balance = u64::from_le_bytes(data[0..8].try_into().unwrap());
    let new_balance = existing_balance
        .checked_add(amount)
        .ok_or(ProgramError::ArithmeticOverflow)?;
    data[0..8].copy_from_slice(&new_balance.to_le_bytes());
    Ok(())
}

Examples

Vulnerable Code

use solana_program::{account_info::AccountInfo, entrypoint::ProgramResult};

// VULNERABLE: raw arithmetic wraps in release mode
pub fn deposit(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let vault = &accounts[0];
    let mut data = vault.data.borrow_mut();
    let existing_balance = u64::from_le_bytes(data[0..8].try_into().unwrap());

    // In release mode: u64::MAX + 1 = 0, not an error
    // Attacker supplies amount = u64::MAX - existing_balance + 1
    // Result: new_balance = 0, but tokens are credited and can be withdrawn
    let new_balance = existing_balance + amount;
    data[0..8].copy_from_slice(&new_balance.to_le_bytes());
    Ok(())
}

Fixed Code

use solana_program::{
    account_info::AccountInfo,
    entrypoint::ProgramResult,
    program_error::ProgramError,
};

// FIXED: checked arithmetic — returns error on overflow
pub fn deposit(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
    let vault = &accounts[0];
    let mut data = vault.data.borrow_mut();
    let existing_balance = u64::from_le_bytes(data[0..8].try_into().unwrap());

    let new_balance = existing_balance
        .checked_add(amount)
        .ok_or(ProgramError::ArithmeticOverflow)?;

    data[0..8].copy_from_slice(&new_balance.to_le_bytes());
    Ok(())
}

// FIXED: Anchor with checked_math! macro
use anchor_lang::prelude::*;

pub fn deposit_anchor(ctx: Context<Deposit>, amount: u64) -> Result<()> {
    let vault = &mut ctx.accounts.vault;
    // checked_math! wraps all enclosed arithmetic in checked variants
    vault.balance = checked_math!(vault.balance + amount)?;
    Ok(())
}

Sample Sigvex Output

{
  "detector_id": "integer-overflow",
  "severity": "high",
  "confidence": 0.72,
  "description": "Add operation on user-controlled variable (function parameter amount) at statement index 4 in function deposit. No checked_add or saturating_add variant is used. Wrapping overflow is possible in release mode.",
  "location": {
    "function": "deposit",
    "offset": 0
  }
}

Detection Methodology

The detector performs a taint-propagation analysis focused on security-relevant arithmetic:

  1. User-controlled variable identification: All function parameters are treated as user-controlled. Any variable assigned from a Load expression (account data read) is also user-controlled. Taint propagates through assignments.
  2. Loop block filtering: Identifies loops with simple counter increments (i < N pattern) and excludes their arithmetic from analysis to reduce false positives.
  3. Security-relevant arithmetic detection: Scans for HirExpr::BinOp nodes with Add, Sub, or Mul operators. Reports only when at least one operand is user-controlled.
  4. Confidence adjustment: Anchor programs receive confidence × 0.85; read-only functions receive confidence × 0.50.

Limitations

False positives:

  • Arithmetic on values that are bounded by a prior require! or if check may be flagged if the constraint does not propagate through the taint analysis.
  • Arithmetic on protocol-controlled constants that happen to be passed as parameters (e.g., fee rates set by admin) may be incorrectly flagged as user-controlled.

False negatives:

  • Overflow via multiple operations (e.g., two consecutive + each below u64::MAX/2) where each individual operation is safe but the chain overflows is not detected.
  • Overflow in complex expressions involving struct field access patterns not yet fully modeled in the HIR.
  • Lamport Drain — detects unauthorized lamport transfers that may exploit arithmetic bugs
  • Missing Signer Check — detects missing authorization before operations that may involve overflow-prone arithmetic

References