Integer Truncation Remediation
How to fix unsafe integer type casts that truncate data.
Integer Truncation Remediation
Overview
Related Detector: Integer Truncation
Unsafe type casts from larger to smaller integer types silently discard high bits, producing incorrect values. The fix is to use try_from/try_into conversions that return errors on overflow, add explicit bounds checks before casting, or redesign to use appropriate types throughout.
Recommended Fix
Before (Vulnerable)
pub fn process(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
// VULNERABLE: truncates values > u32::MAX to wrong amount
let transfer_amount: u32 = amount as u32;
execute_transfer(accounts, transfer_amount as u64)?;
Ok(())
}
After (Fixed)
pub fn process(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
// FIXED: use try_into for safe conversion
let transfer_amount: u32 = amount
.try_into()
.map_err(|_| ProgramError::ArithmeticOverflow)?;
execute_transfer(accounts, transfer_amount as u64)?;
Ok(())
}
Alternative Mitigations
1. Use full-width types throughout
The safest approach is to avoid downcasts entirely:
pub fn process(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
// No cast needed -- use u64 for the entire pipeline
execute_transfer(accounts, amount)?;
Ok(())
}
2. Explicit bounds check before cast
pub fn safe_cast_u64_to_u32(value: u64) -> Result<u32, ProgramError> {
if value > u32::MAX as u64 {
return Err(ProgramError::ArithmeticOverflow);
}
Ok(value as u32)
}
3. Saturating cast with documentation
When truncation is acceptable (e.g., for display or logging), use saturating casts and document the intent:
// Intentional: clamp to u16 range for display purposes only
let display_amount: u16 = amount.min(u16::MAX as u64) as u16;
Common Mistakes
Mistake 1: Casting both sides of a comparison
// WRONG: both sides truncated, comparison is meaningless
if (balance as u32) > (threshold as u32) {
// Large values wrap, comparison fails
}
Compare values in their original type before any casting.
Mistake 2: Casting before arithmetic
// WRONG: truncation happens before the multiplication
let result = (large_value as u32) * rate;
Perform arithmetic at the widest type, then cast the result:
let result: u32 = (large_value * rate as u64)
.try_into()
.map_err(|_| ProgramError::ArithmeticOverflow)?;
Mistake 3: Assuming u64 values always fit in u32
// WRONG: lamport values can exceed u32::MAX (4.29 SOL)
let lamports: u32 = account.lamports() as u32;
Lamport balances are u64 and frequently exceed u32::MAX (4,294,967,295 lamports = ~4.29 SOL). Always use u64 for lamport values.