Improper Error Handling Remediation
How to fix silently ignored errors and improper error propagation.
Improper Error Handling Remediation
Overview
Related Detector: Improper Error Handling
Silently discarded errors allow programs to continue with invalid assumptions, potentially leading to fund loss or state corruption. The fix is to propagate all errors from CPI calls, syscalls, and deserialization using Rust’s ? operator, and to handle error cases explicitly when custom recovery logic is needed.
Recommended Fix
Before (Vulnerable)
use solana_program::program::invoke;
// Vulnerable: ignoring CPI result
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let ix = spl_token::instruction::transfer(
&spl_token::id(), &source, &dest, &authority, &[], amount
)?;
let _ = invoke(&ix, accounts); // Silent failure
mark_withdrawn(accounts, amount)?;
Ok(())
}
After (Fixed)
use solana_program::program::invoke;
// Fixed: propagate all errors
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let ix = spl_token::instruction::transfer(
&spl_token::id(), &source, &dest, &authority, &[], amount
)?;
invoke(&ix, accounts)?; // Failure stops execution
mark_withdrawn(accounts, amount)?;
Ok(())
}
Alternative Mitigations
Custom Error Recovery
When you need to handle specific error cases rather than propagating directly:
match invoke(&ix, accounts) {
Ok(()) => {
mark_withdrawn(accounts, amount)?;
}
Err(e) => {
msg!("Transfer failed: {:?}", e);
return Err(e); // Always propagate or return an error
}
}
Common Mistakes
Mistake: Logging Without Propagating
// WRONG: logs the error but continues execution
if let Err(e) = invoke(&ix, accounts) {
msg!("Warning: transfer failed: {:?}", e);
}
// Program continues as if transfer succeeded
Mistake: Mapping to Ok
// WRONG: converts error to success
let _result = invoke(&ix, accounts).unwrap_or(());
Always ensure errors from state-changing operations halt further state modifications.