We are back with the next part of our series All About Smart Contract Bugs & Security – A cakewalk series, discussing race conditions in a smart contract. Make sure to check out the other parts in the series wherein we talk about Delegate Call Return Value, Stack Size Limit, and many other bugs that can be fatal to your smart contracts.
Very often we hear about race conditions in smart contracts and how these conditions drive a smart contract toward its failure. Surely they sound interesting but what exactly are these conditions? You’re about to find out.
Race Conditions?….Doesn’t Ring a Bell?
Table of Contents
Race conditions are a larger class of bugs that are used as an umbrella term for other bugs in this category, about which we will be talking in further sections of this blog.
Talking about race conditions, this class of bugs can take various forms, and both of the major bugs that led to the infamous DAO’s downfall were bugs of this sort. One of the significant dangers of calling external contracts is that they can take over the control flow, and make such changes to your data that weren’t expected by the calling function.
In simpler terms, what happens is: that users can itemize higher fees to have their transactions mined quicker as miners always get rewarded for running code on behalf of Externally Owned Addresses (EOA). And the public nature of Ethereum allows everyone to view the contents of others’ pending transactions, implying if a given user is revealing a solution or other valuable secret, a hacker can steal that solution and copy their transaction with higher fees to preempt the original one. If developers of smart contracts are incautious, this situation can lead to fatal race conditioning attacks.
Now that that’s out of the way, let’s discuss the different ways in which race conditions can be implemented in a smart contract.
Additional Resource: Top 10 Vulnerabilities in Smart Contract
The Re-entrancy attack, probably the most famous Ethereum vulnerability, took the whole community by surprise when it was observed for the very first time, during a multimillion-dollar heist that led to a hard fork of Ethereum. According to the computer world, when a process is re-entrant, its execution can be interrupted and initiated again and both executions can complete without any errors.
Cross-Function Race Conditions
Similar to the first race condition vulnerability, we have cross-function state dependency.
If there exist two functions relying on the same contract state, it is possible to call one function halfway through the other function’s execution and get undesirable results. Let’s see how.
In the above code, there persists a possibility to call both functions simultaneously, causing userBalances[recipient] += amount to execute, then have msg.sender.transfer fire off in the second function, then have userBalances[msg.sender] -= amount run in the first function. This will result in the user being able to send money to another address and then withdraw (steal) that same amount of money for themselves.
A Snag in Race Condition Solutions
Among all the solutions that you’ll find to combat race conditions, there is one such pitfall that you need to be careful of. Since race conditions can occur across multiple functions, and even multiple contracts, any solution aimed solely at preventing re-entry will not suffice.
Instead, call the external function only after finishing all internal work. However, follow not only avoid calling external functions too soon but also calling functions that call external functions.
This rule, if followed carefully and efficiently, will help you in dodging race conditions in your smart contracts.
This is ImmuneByte’s take on the Race conditions, occurring in smart contracts security and offering hackers a chance to exploit them. Connect with our team to get your smart contract free of any vulnerabilities and loopholes such as this one.
Tune in next Thursday for Part IX of this series wherein we discuss how Ether is lost in transfer while deploying a smart contract.