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.
Recommended Fix
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 Anchor 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.