Unsafe Math
Incorrect arithmetic operations in the codebase can lead to integer overflows/underflows, divisions by zero, conversion truncation/overflow, and incorrect end results. These issues can be exploited by attackers to manipulate calculations and gain an unfair advantage, primarily involving the use of primitive arithmetic operations.
Details
- Related Vulnerabilities: CWE-682: Incorrect Calculation
- Components at Risk: Any part of the blockchain system that uses arithmetic calculations for managing balances, transaction fees, or any other quantitative attributes.
Risks
The primary risks include:
- Manipulation of account balances, leading to unauthorized transfers or artificial inflation of balances.
- Potential disruption of network functionalities that rely on accurate arithmetic calculations.
- Incorrect calculations, leading to unintended consequences such as incorrect account balances or transaction fees.
- Potential for attackers to exploit the vulnerability to manipulate outcomes in their favor.
Mitigation
To address the issues related to unsafe math operations:
- Arithmetic: Use checked or saturated functions like
checked_div
to prevent overflows and ensure safe arithmetic operations. - Conversions: Avoid downcasting values. When necessary, use methods like
unique_saturated_into
instead oflow_u64
to ensure safe conversions. Design systems to avoid the need for downcasting.
Additional Resources
- Link to Official Documentation: Substrate Helper Functions
- Useful Substrate Crates: sp-arithmetic
Case Studies
Frontier Balances
Description
Frontier's CVE-2022-31111 describes issues in converting balances from EVM to Substrate. The pallet did not properly handle conversions, causing the transferred amount to appear differently, which could lead to a possible overflow.
Issue
/// substrate/frame/evm/src/lib.rs
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
fn build(&self) {
for (address, account) in &self.accounts {
let account_id = T::AddressMapping::into_account_id(*address);
// ASSUME: in one single EVM transaction, the nonce will not increase more than
// `u128::max_value()`.
for _ in 0..account.nonce.low_u128() {
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
}
T::Currency::deposit_creating(
&account_id,
account.balance.low_u128().unique_saturated_into(),
);
}
}
}
Solution
/// substrate/frame/evm/src/lib.rs
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig
where
U256: UniqueSaturatedInto<BalanceOf<T>>,
{
fn build(&self) {
const MAX_ACCOUNT_NONCE: usize = 100;
for (address, account) in &self.accounts {
let account_id = T::AddressMapping::into_account_id(*address);
// ASSUME: in one single EVM transaction, the nonce will not increase more than
// `u128::max_value()`.
for _ in 0..min(
MAX_ACCOUNT_NONCE,
UniqueSaturatedInto::<usize>::unique_saturated_into(account.nonce),
) {
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
}
T::Currency::deposit_creating(
&account_id,
account.balance.unique_saturated_into()
);
}
}
}