How Solidity 0.8 protect against integer underflow/overflow and how they can still happen in Solidity 0.8

Faizan Nehal
9 min readMay 14, 2023

--

When solidity 0.8 was rolled out in Dec 2020 it solved a very characteristic problem in the language, integer under & overflow, that used to arise because of how programming languages are designed. Integer overflow and underflow attacks can have significant consequences in a smart contract, potentially leading to security vulnerabilities with loss of funds. This was a major challenge that blockchain developers had to overcome to make sure that their contracts are secure against these flaws and that any malicious actor would not be able to exploit the logic for monetary gain.

For this purpose, several approaches were used before like using an external SafeMath library. Since Solidity 0.8 was released, it solved this issue, and now the compiler checks on each arithmetic operation whether the variables are overflowing or underflowing by default and throws an error accordingly so developers don’t have to focus on the issue. But despite this scrutinization of solidity 0.8 there are several ways through which variables can still overflow. In this article, we will explore how compiler version 0.8 and above protect against overflow and what are the ways through which it can still happen.

What are Integer Underflow/Overflow:

Let’s define what we mean by integer overflow and underflow. They occur when an integer variable exceeds the maximum value that can be stored in that variable type, similarly, an underflow occurs when the variable goes below the minimum value that can be stored. Let’s take an example of uint8 in solidity, the maximum value allowed in uint8 is 255 and the minimum is 0. Now when you store 256 in a uint8 variable, it will overflow and the value will become 0, if you store 257 it will become 1, for 258 it will become 2, and so on. In simple terms when an integer overflow occurs, the value of the integer variable will be reset to the minimum value that can be stored in that variable plus the exceeded value. Similarly, if you try to store -1 in the uint8 variable the value of the variable will become 255, and so on as it will underflow.

Overflow and underflow can also happen if you subtract or add in the minimum or the maximum value stored in the variable. This can cause problems in your code because it can lead to unexpected results. This was one of the most common attack patterns before the advent of solidity 0.8. Suppose you are using an integer variable to keep track of the counter or more specifically to count the number of tokens held by a user, and through a specific functionality the attacker can underflow or overflow the variable then it could lead to repercussion like the variable exceeds the maximum value, it will be reset to the minimum value and start from there all over again. In case of underflow, the value will be set to maximum and the attacker could get an extremely large amount of tokens making it inflationary.

Integer underflow/ overflow is not a design flaw in computer science but it could have serious effects, depending on the program, especially in solidity and other blockchain-specific languages and framework due to the nature of how decentralized applications work considering most of the applications in Web3 are Defi (Decentralized Finance) apps.

In the past many hacks have occurred because of Integer overflow/ underflow vulnerability which include the infamous BeautyChain(BEC) contract attack, hack on the 4chan’s ponzi scheme which was called Proof of Weak Hands Coin(PoWHC), and the attack on Coinstar(CSTR) token that happened because of mintToken function.

How Solidity 0.8 protect against Integer Underflow/Overflow:

When Solidity 0.8 was launched in December 2020, it resolved this issue in the language by making some groundbreaking alternation, particularly by changing the opcodes in the language for arithmetic operations which now have built-in overflow and underflow checks and reverts upon failure.

So, how does Solidity 0.8 protect against integer overflows and underflows?

One way is through the use of built-in safe math functions. These functions, which are provided as part of the Solidity standard library, include add, sub, mul, and div, and they perform arithmetic operations on integers while checking for overflows and underflows. If an overflow or underflow occurs as a result of the operation, the function will throw an exception and the operation will not be carried out.

There are also opcodes that help the compiler in detecting overflows and underflows in the code, some of the common ones are ADDMOD, MULMOD and SMOD. The way they work are defined as follows:

ADDMOD: The first opcode is ADDMOD, this opcode performs an addition operation on two integers, takes modulus, and checks whether the remainder is greater than the maximum value of datatype, if the remainder is greater than the allowed datatype then the transaction would revert.

Suppose in the case of two integers a and b of type uint256 ADDMOD will do the operation (a+b) % N, where N is the maximum value of the datatype, and checks whether the result is greater than 2²⁵⁶-1 if it would be greater than the transaction would revert otherwise not. Similarly, for uint8 it will check the return against 2⁸-1 and so on.

MULMOD: Similar to ADDMOD, this code is used for multiplication operations. It takes two integers a and b and sends them through the formula (a*b) % N. If the result is greater than the uint, for uint256 it would be 2²⁵⁶-1, then the opcode will revert the transaction otherwise not.

SMOD: SMOD performs the unsigned modulo operation but returns the signed result. If the result of the modulo operation would cause an integer overflow the smod opcode will return zero instead of the result of the modulo operation.

These new operations can be used to perform arithmetic operations safely, as they will prevent integer overflow and underflow from occurring. Besides these opcodes, Solidity 0.8 also introduced a new type called uint256x, which is a fixed-size integer type with 256 bits of precision. This type is useful for cases where very large integer values are needed, as it does not suffer from integer overflow or underflow.

How Integer Underflow/Overflow Is Still Possible In Solidity 0.8:

Even after the update of solidity to 0.8, there are scenarios in which the integer overflow and underflow can still occur and in some cases, they can be exploited. Blockchain developers must be aware of these scenarios in order to safeguard their smart contracts from any potential vulnerabilities.

Typecasting:

The most common scenario where an integer underflow or overflow is possible is in Typecasting. This happens when you convert an integer of a larger uint data type to a smaller data type. Suppose you have stored the integer 256 in a variable of type uint256 and now you want to convert it into uint8. If you want to convert this integer from a variable of type uint256 to a variable of type uint8, you must be aware that doing so will cause the value to wrap around, overflow, and become 0. The variable will overflow and the solidity compiler won’t throw an error if done this way.. The simple solidity code that follows is a nice illustration of how overflow with typecasting can happen and how the solidity compiler overlooks it.

In the above code snippet, the value stored in b will be 9 instead of 265due to the fact that maximum value in uint8data type can be 255, so it will overflow and reset from 0. This is the simplest example of how typecasting allows integer overflow/ underflow in solidity 0.8. The code can be something complex that developers might miss during development.

Shift Operator:

The second way through which an integer overflow can occur is the use of a shift operator. Keep in mind that overflow & underflow checks are never performed for shift operations as they are performed for other arithmetic operations. The left shift << operator frequently causes integer overflow.

The following is the formula for left shift operation:

a << n = a * 2^n

This operator shifts all the bits in the first operand by the number specified in the second operand. Shifting an operand by 1 position is equivalent to multiplying it by 2, shifting 2 positions is equivalent to multiplying it by 4, and shifting 3 positions is equivalent to multiplying by 8. The below-specified code will overflow in solidity:

In the given code, left shifting a which is 100 by 2 positions b is equivalent to multiplying 100 by 4. So the result will be 400, but the maximum value allowed in a uint8 is 255, so the result will overflow and the value in c will be 144 because 400-256 is 144.

Inline Assembly:

The use of YUL or inline assembly in a solidity smart contract also makes integer overflow/ underflow possible even if the compiler version of solidity is 0.8. In YUL programming language, integer underflow & overflow is possible in the same way as Solidity and it does not check automatically for it as YUL is a low-level language that is mostly used for making the code more optimized, which does this by omitting many opcodes. Because of its low-level nature, YUL does not perform many security checks therefore it is recommended to use as little of it as possible in your smart contracts.

In the above code we are adding 1 into the variable a with inline assembly and then returning the result. Notice that the variable result will overflow and 0 will be returned, despite this the contract will not throw an error or revert.

Unchecked Code Block:

The last one is pretty obvious, which is to use the unchecked code block. Performing arithmetic operations inside the unchecked block saves a lot of gas because it omits several checks and opcodes that are used for overflow and underflow. The unchecked code blocks are very helpful in operations like looping through an array where it usually takes a lot of unnecessary gas for checks or doing arithmetic operations with no chance of over/underflow.

The purpose of an unchecked code block is to get granular control over the operations and write an efficient contract by skipping many opcodes that are used in default arithmetic operations in 0.8 to check for underflow/ overflow. The unchecked code block is only recommended if you are sure that there is no possible way for the arithmetic to overflow or underflow.

Conclusion:

Integer underflow and overflow are potential security vulnerabilities that can occur in Solidity code when arithmetic operations are performed on integers. These issues can lead to unintended end results and can potentially be exploited by attackers. Developers need to be aware of these issues and all the scenarios they can arise in and to properly handle them in their code to prevent potential exploits. In Solidity 0.8, new features such as the SafeMath library have been introduced to help mitigate the risk of integer underflow and overflow. It is recommended that developers use the updated compiler version to protect their smart contracts from these types of vulnerabilities. And despite using the updated version of Solidity the integer overflow/ underflow can still rise without even the developers noticing them therefore they need to be aware of the edge cases.

--

--

Faizan Nehal
Faizan Nehal

Written by Faizan Nehal

I am an independent cyber security researcher and ethical hacker. Always trying to improve myself and learning something new.