Arithmetic overflow

Arithmetic overflow in Substrate occurs when arithmetic operations are performed using primitive operations instead of specialized functions that check for overflow. When a Substrate node is compiled in debug mode, integer overflows will cause the program to panic. However, when the node is compiled in release mode (e.g. cargo build --release), Substrate will perform two's complement wrapping. A production-ready node will be compiled in release mode, which makes it vulnerable to arithmetic overflow.

Example

In the pallet-overflow pallet, notice that the transfer function sets update_sender and update_to using primitive arithmetic operations.

#![allow(unused)]
fn main() {
   /// Allow minting account to transfer a given balance to another account.
   ///
   /// Parameters:
   /// - `to`: The account to receive the transfer.
   /// - `amount`: The amount of balance to transfer.
   ///
   /// Emits `Transferred` event when successful.
   #[pallet::weight(10_000)]
   pub fn transfer(
       origin: OriginFor<T>,
       to: T::AccountId,
       amount: u64,
   ) -> DispatchResultWithPostInfo {
       let sender = ensure_signed(origin)?;
       let sender_balance = Self::get_balance(&sender);
       let receiver_balance = Self::get_balance(&to);

       // Calculate new balances.
       let update_sender = sender_balance - amount;
       let update_to = receiver_balance + amount;
       [...]
   }
}

The sender of the extrinsic can exploit this vulnerability by causing update_sender to underflow, which artificially inflates their balance.

Note: Aside from the stronger mitigations mentioned below, a check to make sure that sender has at least amount balance would have also prevented an underflow.

Mitigations

References

  • https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow
  • https://docs.substrate.io/reference/how-to-guides/basics/use-helper-functions/