Arithmetic Overflows and Underflows in Ethereum Virtual Machine
Ethereum Virtual Machine (EVM), defines a constant size for all data types and so for the integers. Thus, a variable is used to store an integer, there is a specific range of numbers this variable can take. For example, a uint8 variable, can store numbers between 0 and 255.
Arithmetic Overflow is a condition that occurs when an operation produces a result that is greater than a given register or storage location can store or represent.
Consider we are having a uint8 variable called sum. This variable can take values from 0 to 255 as the scope of a uin8 variables is [0, 255]. The variable sum is the sum of the variable a (where a = 0) and b (where b = 257).
If we add a and b we get sum = a + b => sum = 0 + 257 => sum = 257. At that point notice that sum = 257 ∉[0, 255]. In order to store this value in the sum variable we have to wrap around the number 257 and convert it into a number which exists inside the scope [0, 255]. For this process we use the following formula:
(number To Be Added — maximum) + minimum — 1
By using this formula we have sum = (257–255) + 0–1 => sum = 1 ∈ [0, 255]. So, after 257 wrapped around it converted into 1 which belongs in the scope of [0, 255]. Now the value 1 can normally stored into sum variable.
This condition is called overflow. When an overflow occurs the EVM uses the wrap around process so it can store the value into the variable.
We can now follow the same process when an underflow occurs. Consider again we are having a uint8 variable called sub. This variable can take values from 0 to 255. The variable sub is the subtraction of the variable a (where a = 0) minus b (where b = 1).
If we now subtract a from b we get sub = a — b => sum = 0–1 => sub = -1. At that point notice that sub = -1 ∉ [0, 255]. In order to store this value in the sub variable we have to wrap around again the number -1. For this process we use the following formula:
maximum + (minimum — numberToBeSubtracted) + 1
By using this formula we have sub = 255 + (0–1) +1 => sub = 255 ∈ [0, 255]. So, after -1 wrapped around it converted into 255 which belongs in the scope of [0, 255]. Now the value 255 can normally stored into sum variable.
The following table shows how uint8 values are converted after the wrap around process.
This vulnerability can occur when a mathematical equation uses a fixed size variable to store a value which is out of its range. Token contract uses the transfer function to check if the user has the required balance in order to transfer the desired amount to an address.
Consider that the user has a zero balance and willing to transfer 100 ether. When line 11 inside transfer function executed the following will happen.
balances[msg.sender]-value = 0–100 = -100
Notice that we have a uint8 variable so -100 ∉[0, 255]. So we have an underflow. As we told before the wrap around process will be executed so value -100 will be converted into 156. Now 156 ∈ [0, 255] and the following will happen.
balances[msg.sender]-value = 156 = -100 ≥ 0 = True
So what the malicious user has done here is that successfully transferred 100 ether from a zero balance account.
To avoid this type of attack we can use in our code mathematical libraries. Those libraries replace every operator with fixed functions.
One of the most popular libraries for this reason is the Safe Math Library from Open Zeppelin. This library replace all the mathematical operators with functions and whenever an underflow or overflow occurs the function throws an error and the program terminates without cause any confusion.
So, now if we use Safe Math Library, the code Token Contract will be the following.
When line 11 executed and an underflow occurred, the function will throw an error and the program will terminate.