Skip to main content
Sigvex

Lamport Underflow

Detects lamport transfers that may exceed available balance, causing underflow, account closure, or rent-exempt violations.

Lamport Underflow

Overview

The lamport underflow detector identifies lamport transfer operations that could drain more lamports than an account contains. It detects three patterns: transfers using unchecked arithmetic in the amount, multiple consecutive transfers from the same account without cumulative balance validation, and full balance transfers that do not reserve the rent-exempt minimum. For remediation steps, see the Lamport Underflow Remediation.

Why This Is an Issue

Lamport transfers that exceed an account’s balance cause different failure modes depending on context:

  1. Transaction failure and DoS: The Solana runtime rejects transactions where an account’s lamport balance goes negative. Attackers who can trigger this failure path cause denial-of-service.
  2. Account closure: Transferring all lamports from an account closes it, destroying any stored data. If the account is needed for subsequent operations, this causes permanent data loss.
  3. Rent-exempt violation: Accounts that fall below the rent-exempt minimum are subject to rent collection and eventual garbage collection, causing gradual data loss.
  4. Arithmetic underflow in amount: When the transfer amount itself is computed using unchecked arithmetic (e.g., base + fee), an overflow in the computation can produce an amount that exceeds available balance.

Multiple transfers from the same source account compound the risk – each individual transfer may be within bounds, but their cumulative total may exceed the balance.

How to Resolve

// Before: Vulnerable -- no balance check, unchecked arithmetic
let amount = base_amount + fee;  // Can overflow
**source.try_borrow_mut_lamports()? -= amount;
**dest.try_borrow_mut_lamports()? += amount;

// After: Fixed -- checked arithmetic and balance validation
let amount = base_amount.checked_add(fee)
    .ok_or(ProgramError::ArithmeticOverflow)?;
let balance = **source.try_borrow_lamports()?;
if balance < amount {
    return Err(ProgramError::InsufficientFunds);
}
**source.try_borrow_mut_lamports()? -= amount;
**dest.try_borrow_mut_lamports()? += amount;

Examples

Vulnerable Code: Multiple Transfers

pub fn distribute(accounts: &[AccountInfo]) -> ProgramResult {
    let source = &accounts[0];
    // No cumulative balance check
    **source.try_borrow_mut_lamports()? -= 1_000_000;
    **accounts[1].try_borrow_mut_lamports()? += 1_000_000;

    **source.try_borrow_mut_lamports()? -= 2_000_000;
    **accounts[2].try_borrow_mut_lamports()? += 2_000_000;
    // Source may not have 3,000,000 lamports total
    Ok(())
}

Vulnerable Code: Full Balance Drain

pub fn close_account(accounts: &[AccountInfo]) -> ProgramResult {
    let source = &accounts[0];
    let dest = &accounts[1];
    let balance = **source.try_borrow_lamports()?;

    // Transfers entire balance -- account becomes unusable
    **source.try_borrow_mut_lamports()? -= balance;
    **dest.try_borrow_mut_lamports()? += balance;
    Ok(())
}

Fixed Code

pub fn distribute(accounts: &[AccountInfo]) -> ProgramResult {
    let source = &accounts[0];
    let total_needed = 1_000_000u64
        .checked_add(2_000_000)
        .ok_or(ProgramError::ArithmeticOverflow)?;

    let balance = **source.try_borrow_lamports()?;
    if balance < total_needed {
        return Err(ProgramError::InsufficientFunds);
    }

    **source.try_borrow_mut_lamports()? -= 1_000_000;
    **accounts[1].try_borrow_mut_lamports()? += 1_000_000;

    **source.try_borrow_mut_lamports()? -= 2_000_000;
    **accounts[2].try_borrow_mut_lamports()? += 2_000_000;
    Ok(())
}

Example JSON Finding

{
  "detector": "lamport-underflow",
  "severity": "critical",
  "confidence": 0.78,
  "title": "Full Balance Transfer Without Rent-Exempt Reserve",
  "description": "Account transfers entire lamport balance without reserving rent-exempt minimum.",
  "cwe_ids": [672]
}

Detection Methodology

  1. Transfer collection: Scans all blocks for TransferLamports statements, grouping by source account variable.
  2. Arithmetic check: Examines transfer amount expressions for unchecked arithmetic (add, sub, mul operations).
  3. Multiple transfer analysis: Flags accounts with two or more transfers when no balance validation (comparison with AccountLamports) is found between them.
  4. Full drain detection: Identifies transfers where the amount expression is AccountLamports (entire balance), checking whether rent-exemption syscalls appear in the function.

Limitations

False positives: Programs that validate cumulative balance in called helper functions will still be flagged. Intentional account closure (where draining is expected behavior) triggers the rent-exempt finding. Programs where multiple transfers are in different execution branches (not cumulative) may be incorrectly flagged. False negatives: Balance validation performed via CPI or complex conditional chains may not be recognized. Transfers performed via CPI to the System Program (rather than direct lamport manipulation) are not detected.

References