Skip to main content
Sigvex

Division by Zero Remediation

How to fix division operations where the divisor may be zero, preventing panics and DoS.

Division by Zero Remediation

Overview

Related Detector: Division by Zero

Division by zero in Solana BPF programs causes a runtime panic that crashes the transaction. Attackers exploit this for denial-of-service by submitting zero divisors. The fix is to validate divisors before every division and modulo operation, or use checked_div/checked_rem which return None on zero.

Before (Vulnerable)

pub fn calculate_share(total: u64, num_recipients: u64) -> ProgramResult {
    // VULNERABLE: panics if num_recipients == 0
    let share = total / num_recipients;
    let remainder = total % num_recipients;
    Ok(())
}

After (Fixed)

pub fn calculate_share(total: u64, num_recipients: u64) -> ProgramResult {
    // FIXED: explicit validation + checked arithmetic
    if num_recipients == 0 {
        return Err(ProgramError::InvalidArgument);
    }

    let share = total
        .checked_div(num_recipients)
        .ok_or(ProgramError::ArithmeticOverflow)?;
    let remainder = total
        .checked_rem(num_recipients)
        .ok_or(ProgramError::ArithmeticOverflow)?;
    Ok(())
}

Alternative Mitigations

1. Default value on zero

When zero has a meaningful interpretation (e.g., “no stakers means no distribution”):

pub fn per_staker_reward(total: u64, stakers: u64) -> u64 {
    if stakers == 0 {
        return 0;  // No stakers, no distribution
    }
    total / stakers
}

2. Clamp divisor to minimum

pub fn calculate_rate(amount: u64, period: u64) -> Result<u64, ProgramError> {
    // Ensure period is at least 1
    let safe_period = period.max(1);
    Ok(amount / safe_period)
}

3. Use an account-validation framework constraints for input validation

#[derive(Accounts)]
pub struct Distribute<'info> {
    #[account(
        constraint = config.num_recipients > 0 @ ErrorCode::NoRecipients
    )]
    pub config: Account<'info, DistributionConfig>,
}

Common Mistakes

Mistake 1: Checking only division, not modulo

if divisor == 0 { return Err(...); }
let quotient = total / divisor;
let remainder = total % divisor;  // Also needs the check!

Both division and modulo panic on zero. Validate once, then use for both operations.

Mistake 2: Checking after the division

// WRONG: division already executed (and panicked) before the check
let result = amount / rate;
if rate == 0 {
    return Err(ProgramError::InvalidArgument);
}

Always validate the divisor before performing the operation.

Mistake 3: Trusting derived values without validation

// WRONG: supply could be 0 even if derived from "trusted" source
let supply = token_account.supply;
let price_per_token = total_value / supply;

Even values from on-chain accounts can be zero (newly created token mints, depleted pools). Always validate.

Mistake 4: Using unwrap() on checked division

// WRONG: defeats the purpose of checked arithmetic
let result = total.checked_div(divisor).unwrap();

Use .ok_or(error)? to propagate the error gracefully.

References