Skip to main content

Integer overflow or underflow

Description

This type of vulnerability occurs when an arithmetic operation attempts to create a numeric value that is outside the valid range in substrate, e.g, an u8 unsigned integer can be at most M:=2^8-1=255, hence the sum M+1 produces an overflow.

Exploit Scenario

There follows a snippet of a simple ink! smart contract that is vulnerable to an integer overflow vulnerability.

#[ink(message)]
pub fn add(&mut self, value: u8) {
self.value += value;
}

#[ink(message)]
pub fn sub(&mut self, value: u8) {
self.value -= value;
}

The above contract stores a single value of type u8 and provides three functions allowing interaction with the single value. The add() function allows users to add a specified amount to the stored value, the sub() function allows users to subtract a specified amount, while the get() function allows users to retrieve the current value.

This contract is vulnerable to an integer overflow attack that may be exercised if a user adds a value that exceeds the maximum value that can be stored in an u8 variable, then the addition operation overflows the variable and the value wraps to zero (ignoring the carry), potentially leading to unexpected behavior.

The vulnerable code example can be found here.

Deployment

Before deployment, the contract must be built using the tool cargo-contract:

cargo contract build --release

Following that, the contract can be deployed either by using cargo-contract or a GUI tool (e.g., https://contracts-ui.substrate.io/):

cargo contract instantiate --constructor new --args 0 --suri //Alice

Remediation

It is recommended that the code be changed to explicitly use checked, overflowing, or saturating arithmetic. For example:

#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
pub enum Error {
/// An overflow was produced while adding
OverflowError,
/// An underflow was produced while substracting
UnderflowError,
}

The problematic functions can be updated as follows:

#[ink(message)]
pub fn add(&mut self, value: u8) -> Result<(), Error> {
match self.value.checked_add(value) {
Some(v) => self.value = v,
None => return Err(Error::OverflowError),
};
Ok(())
}

#[ink(message)]
pub fn sub(&mut self, value: u8) -> Result<(), Error> {
match self.value.checked_sub(value) {
Some(v) => self.value = v,
None => return Err(Error::UnderflowError),
};
Ok(())
}

The remediated code example can be found here.

Other rules could be added to improve the checking. The set of rules can be found here.

References