Integer overflows are a tricky beast! This article is your free ticket, not just to understanding integer overflows but, to discovering new preventative measures.
If you are not a software developer, please read on, integer overflows are fascinating. Also, I have worked really hard to make this article easy to digest; the maths and coding are on the light-and-easy side, so have a little fun.
If you are a software developer, stick around until the end, because you will get a chance to deploy your very own smart contract on a blockchain. It’s fast and it’s free!
We are going to start off this article by providing you with a few, easy to understand, demonstrations; written in Rust. There are two good reasons for using Rust.
1 — Rust handles integer overflows differently depending on whether you are:
- compiling your program in debug mode, or
- compiling your program for release
This is really handy for demonstration purposes!
2 — Secondly, there is a chance that you could be writing a blockchain smart contract in Rust before you know it.
Did you know that one of the goals of Ethereum’s Web Assembly (WASM) project called eWASM, is to provide a library and instructions for writing smart contracts in Rust?
Ready? Let’s dive in…
Rust’s signed integer i8
This Rust data type will accept values from -(2 ⁸⁻¹) right through to 2⁸⁻¹ -1.
These values can also be expressed as -(2⁷) right through to 2⁷ -1.
Or -(2x2x2x2x2x2x2) right through to (2x2x2x2x2x2x2) -1.
Perhaps, put more simply, -128 right through to 127.
Armed with that knowledge, let’s create an overflow issue. In the code below, we start by declaring an i8 variable called i_8_int. We then assign the number 127 to the variable called i_8_in.
127 is the upper bound of this variable type. So, if we increment the variable by one, we will see that the new value, of i_8_in, goes from 127 to -128 (negative 128)!
As we go through these examples, think of an analogue wall clock.
Imagine that you are incrementing the hour hand. The clock will never read 12 o’clock then 13 o’clock. The clock will always read 12 o’clock then go back around to 1 o’clock (starting again back at the beginning).
Now imagine -128 to 127 in a circular fashion. Once the value has gone all of the way around, it will proceed from 127 to negative 128. This is known as wrapping, or returning a wrapped result.
Signed integers are called signed due to the fact that they can be negative i.e. -128, -127, -126 … 0 1, 2, 3 … 126, 127. Unsigned integers are only positive numbers. You can also imagine these numbers in a circle (like a clock face, except one (with no negative values) which starts at zero and then wraps around again i.e. 0, 1, 2, 3 … 253, 254, 255, 0, 1, 2).
Rust’s unsigned integer u8
This data type will accept values from 0 right through to 2⁸ -1.
These values can also be expressed as 0 right through to (2x2x2x2x2x2x2x2) -1.
Again, perhaps put more simply, 0 right through to 255.
Which integers will overflow/wrap?
This overflows issue will repeat for all of the integer variable types, as you can see from the i64 (signed integer) example below.
Why are integer overflows so important? — Part 1
No matter what programming language you are using, it is very important to ensure that the behaviour of your code is what you actually intended.
Integer overflows can occur in many languages with catastrophic results, as this article explains, and the video below demonstrates.
The Ariane 5 rocket self-destructed because of a data conversion! The original 64-bit floating point value was too large to be represented by a 16-bit signed integer.
Back to Rust
We mentioned at the beginning that Rust will handle overflow errors differently depending on whether you are compiling in debug mode or in release mode.
The following code shows how our first demonstration of integer overflow successfully compiles using Rust’s release mode. This code will return the wrapped result in the event of an overflow. Something to be aware of!
If we run the same program using debug mode, the program will not return a wrapped result, but instead will “panic” as shown below.
Note that the panic from the compiler explicitly points out the overflow on line 4 of the above source code (where we try and increment the value of the i_8_int variable).
Why are integer overflows so important? — Part 2
An example of an open system is a smart contract running on a public blockchain. Why? Because, in this scenario any end-user could pass almost any variable, that they like, into your smart contract’s function. We have an example coming up where someone created millions of free tokens by passing in values which caused an integer overflow.
To demonstrate “external user input”, (as apposed to having the overflow values statically typed into the source code, like before) let’s explore the possibility of a program which is susceptible to an overflow attack from an external source.
Here, we let the end-user pass in their own values into a Rust program. As you will see, anyone can induce an overflow.
“Release” mode, again
As we can see from the output of the execution below, we are able to create an overflow by submitting in the upper bound of i8 (127) and then submitting 1. Note 127 + 1 = -128. In this scenario, there is no panic, the code returns the wrapped result.
“Debug mode”, again
Interestingly, even though the numbers in this program are 100% passed in by external users, the Rust compiler in “debug” mode still catches this overflow and panics. This is great!
Analyzing blockchain programming
A real world example of blockchain integer overflow
The following is a walkthrough of the code from the BeautyChain (BEC) contract. As this PeckShield article points out, a single line of code in the BeautyChain contract allowed an integer overflow to occur.
The outcome was a really weird one. Instead of stealing some, or all, of the 7000000000000000000000000000 tokens in supply. The attackers were able to send the equivalent of 2²⁵⁶ tokens between two separate (new) accounts owned by the hackers. Over and over again. Each attack essentially sent 115792089237316195423570985008687907853269984665640564039457584007913129639935 tokens to the hackers.
How was this done? The source code, reveals it all. However, if you don’t want to look at 299 lines of code, no problem! Here is a very “short” plain english version of events.
The short version of the BEC hack
The source code of the BEC token provided an opportunity for a hacker to enter a value which when multiplied by 2 would equal 0 (thanks to an integer overflow).
Let’s take a quick look at how the hack works. Basically we just have to pass in values which will make the “amount” on line two of the function become 0.
Here is one method. If we pass in a _value which is half of 2 ²⁵⁶ and then enter two account addresses for the _receivers, the following will happen, in the first two lines of the above function.
uint cnt will be equal to 2;
uint256 amount will be equal to 0;
… and that’s about it. If “amount” is equal to zero, then the code that follows will deposit the _value into the two accounts and take zero from the account which is calling the function (the hackers account).
Did you follow? Or are you wondering where the zero came from?
Let’s strip it down, remember, we said that an 8 bit unsigned integer is 2⁸ -1 or (2x2x2x2x2x2x2x2) -1, or 0 through 255.
We also said that 255 + 1 = 0. Think of this, again, in terms of numbers arranged in a circle, like the face on a wall clock. We have values from 0 to 255 (256 slots). One slot for 0 and many more for the other 255 numbers. If we go beyond the 255th slot we end up back at the number zero (not 256).
Half of 256 is 128, and therefore 128 * 2 = 0. This theory works for all integer sizes.
Thinking about blockchain smart contracts
In the world of smart contract’s, developers can utilise libraries to perform safe calculations (prevent integer overflows). One example of an extremely comprehensive library for secure smart contract development is OpenZeppelin.
I took a leaf out of the OpenZeppelin book and created the following “i8_safe_add” function below to demonstrate how developers can prevent integer overflow issues by writing better code.
The code compiles successfully in release mode (without any debugging or overflow checking at the compiler level).
The following is an example of the output when a user inputs values which do not induce an overflow.
The following is an example of the output when a user inputs values which do induce an overflow.
As you can see the program, with the new i8_safe_add code, correctly quits as soon as the overflow is detected.
But, code is never perfect!
We may never see a situation where software developers write perfect code. There will always be that one in a million instance where something is accidentally overlooked. Developers are busy, programming is hard and we are all human after all.
A recent study analyzed nearly one million deployed Ethereum smart contracts and found that many of these contracts contained serious vulnerabilities … Ethereum’s newest programming language, Vyper, strives to provide superior audit-ability … Vyper is being developed on the principle that it should be virtually impossible for developers to write misleading code .
We can not force developers of free and open source software (FOSS) to use safe software libraries or even write unit tests. There is also no global body in charge of auditing every line of code which is written.
So how do we solve this problem?
The ultimate solution
The e-commerce blockchain CyberMiles have a multi-prong approach to integer overflows. CyberMiles proactively eliminate (safeuint, uint256 and int256) integer overflows in smart contract code  by:
- supporting a new safeuint data type, whereby all safeuint operations are automatically wrapped in SafeMath functions, and,
- detecting integer overflows at runtime; halting contract execution in the event of an integer overflow
The CyberMiles documentation demonstrates how they are even able to compile the above BEC contract and then execute its problematic batchTransfer function, without any integer overflow issues. This is thanks to their Virtual Machine’s built in integer overflow protection mechanism, which protects all smart contract developers and end-users in the CyberMiles ecosystem.
Create your own smart contract
Let’s dive in and deploy a quick smart contract on the CyberMiles testnet. All you need is a Chrome web browser. Seriously! The rest is easy.
Go ahead and grab the CyberMiles MetaMask plug-in; the link will take you to the official Google Chrome store.
Go ahead and get your 1000 free CMT tokens from the CyberMiles testnet faucet.
Within mere seconds your MetaMask wallet will display the 1000 new tokens.
Open up the CyberMiles free online smart contract editor and paste in some raw code (click on “view raw” in the bottom right of the image below).
The code will auto-compile. If not, click the “Start to compile” button in the Compile Tab of the free online code editor.
Switch over to the Run tab (in between Compile and Settings tabs) and click the red “Deploy” button.
Keep an eye out for the CyberMiles MetaMask pop-up because you will need to “CONFIRM” these transactions with a single mouse click. If you do not get the pop-up, look in the upper right side of your Chrome browser for the notification (1).
The pop-ups are pretty obvious, so you should not have any issues.
Test the contract by passing in two safeuint variables. In the example below, we add the numbers 1 and 1. We then click the red “transact” button. You will get another pop-up to “CONFIRM”.
To check the output, click on the green tick in the box beneath your code. This will open up the latest transaction and allow you to view the logs.
You will see, once you scroll down to the logs, that the logs show the correct values. _value1 is 1, _value2 is 1 and the result is 2. It’s true 1+1=2.
If we remember back to our theory about integers we will recall (or can now calculate on our own) that an unsigned 256 bit integer will have a maximum value of 115792089237316195423570985008687907853269984665640564039457584007913129639935.
Remember the theory from above 2x2x2x2x2x2x2x2x2x2 and so forth?
If you repeat the above test again using a number which will push the upper bounds of uint256, you will see that the CyberMiles blockchain will reject the transaction.
For example, if you try and add the above number and the number 1 (as the two arguments to the “invokeOverflow” function of your “TestIntegerOverflow” smart contract), you will get the following warning.
Essentially, what this message is telling you is that this transaction will likely fail. The message is giving you a chance to gracefully bow out. If you still proceed however, the Virtual Machine will be forced to consume all of the gas which you provided. But, will also ultimately reject the transaction because of the incorrect operation involving the uint256 integer.
We mentioned at the start of this article that the Rust programming language may become one of the smart contract programming languages for Ethereum 2.0 smart contracts. More specifically, eWASM.
As we have seen above, Rust will allow integer overflows to compile using “release” mode. This is completely normal.
Deliberate integer overflows
It would be remiss of me now to mention that wraparound behaviors have their place in some software implementations. Research shows that many integer overflows in low level code are intentional and that, the intentional use of wraparound behaviors are actually more common than is widely believed .
Unwanted integer overflows
However, as we have seen, it is highly unlikely that blockchain smart contract code will ever need to intentionally utilise wraparound behaviors. The realisation of eWASM may occur sooner than we think and this is an area of research and development that needs to be performed sooner rather than later. I would love to hear your thoughts on how eWASM could be designed to allow developers the freedom to code how they wish, whilst perhaps building in some security measures. Any upfront ideas and designs will surely have a positive impact on the future of the decentralized web, and all of its users.
Please applaud if you liked this article. Leave a comment if you have any questions. See you again soon!