‘Curve Overtaking’ to deceive smart contract rules | Chengdu LianAn Technology Vulnerability Analysis Series Phase III
Reentrancy and Race condition jointly threaten assets, use limits and interactions to eliminate the threat.
Whilst the ‘highway’ of blockchain becomes more and more crowed, the accidents come. The reason of this is that large number of investors and project initiators flock in with the ambition of earning money as fast as possible, like racing in ‘Fast and Furious’. However, there are people trying to hack the race. These hackers can always make smart contracts vulnerable because quality contract could take more time to develop.
This time, we will talk about two types of race condition vulnerabilities, reentrancy and transaction ordering dependence (TOD).
In April, 2016, the fully autonomous, decentralized project DAO was initialized, it became popular very fast. However, it received warnings from some developers saying the splitDAO function contains recursive call problem. On 14th, the DAO stated that the vulnerability has been located, assets have been protected and contract security has been ensured.
Who’d have thought that just 3 days later, 17th June, hacker used the exact same vulnerability to attack DAO, 3.6 million ethers were in danger, finally, there were over 6-million-dollar worth of ether being transferred out ceaselessly.
After the attack, the DAO project sponsor undertook some measures to slow down the loss, and Ethereum also took some action to help the DAO to transit the rest of the money and to retrieve the lost part, but these actions caused the hard fork of the Etherum.
To analysis the methods hacker used, Race Condition needs to be introduced first.
What is race condition?
A race condition is a condition that requires operations of a device or system to be done in the proper sequence to be done correctly, otherwise there will be undesirable situation causing malfunctions.
In smart contracts, when race condition vulnerability is exploited by attacker, attacker will use an attack contract to take control and alter the behavior of the contract.
If we use an analogy to describe it, we can compare smart contract to a highway, all functions are cars on it, the original execution order regulated the sequence of the cars. Suddenly, a skilled driver drives a GTR overtaking at the curve, disrupting the original sequence. Furthermore, the driver also takes the leading position and changes the rules of the highway.
One of the characteristics of Ethereum is that it can call and use external contracts. However, the potential risk is that external contract may take over control flow, and alter data of calling function unexpectedly. This type of vulnerability shows in several forms, here we focus on Reentrancy and Transaction-Ordering Dependence (TOD).
Race Condition Vulnerability Analysis and Detailed Bug-Fixes
The contracts nowadays are normally used for dealing with Ether, so usually they are responsible for sending Ether to various external user addresses. To call external contracts or to send Ether to certain addresses requires submitting external call requests. These external calls might be hijacked by attacker to force contract to execute further code (namely going through fallback function), including fallback function itself. Therefore, the execution will re-enter the contract. This type of attack was used for the infamous DAO attack.
We simplify the bugged contract to the following sample contract:
This contract contains 2 functions: depositFunds() and withdrawFunds(). The function of depositFunds() is to increase the balance of msg.sender. While the function of withdrawFunds() is to withdraw Ether with the appoint value of _weiToWithdraw
Now, let’s say an attacker creates a contract like this:
PS: Beware that because of reentrancy attack, balances[msg.sender] overflows. So we highly recommend all mathematics should use SafeMath. We’ve already mentioned this in Phase I.
Now let’s analyze how this contract conducts reentrancy attack:
1. Assume that a normal user deposit 15 Ether into the original contract (Reentracny.sol).
2. Attacker deploys attack contract (POC.sol), and calls setInstance() to point to the deployment address of original contract.
3. Attacker calls depositEther() in the attack contract to deposit 1 Ether into the original contract beforehand. At this moment, from the aspect of the original contract, the address of attack contract has 1 Ether balance.
4. The attacker calls the withdrawFunds() function in the attack contract, this function calls the withdrawFunds() in the original contract to transfer out 1 Ether.
5. Look into the original contract, the first line of withdrawFunds() function — require(balances[msg.sender]>=_weiToWithdraw);, The attack contract address has 1 Ether, is equal to _weiToWithdraw, meeting the requirement. So the execution processes to the next line.
6. The second line of withdrawFunds() function is require(msg.sender.call.value(_weiToWithdraw)());, which means transferring _weiToWithdraw(1 Ether at the moment) to msg.sender. Since Solidity regulates that if no other valid functions are appointed, and msg.sender is a contract address. The default result is that the fallback function will be called. In this occasion, execution will go into attack contract and call the fallback function of attack contract. Besides, because call.value()() is called to send Ether, this method will send all the rest gas.
7. The execution processes into the fallback function of attack contract, and the ‘if’ is used to check the balance of the original contract. At this moment, the balance is 16 Ether, which meets the requirement, so the execution ‘reenter’ the withdraw() function of the original contract.
8. Since the balances[msg.send]-=_weiToWithdraw was not executed, so the address of attack contract still has 1 ether, which meets the requirement of the ‘require’. Hence the execution of the second ‘require’ in the withdrawFunds() function in the original contract.
9. From this point, step 6–8 will always repeat until the balance of the original contract is deducted to lower than 1 Ether or all gas is drained.
10. Finally, the execution enters the original contract to process balances[msg.sender]-=_weiToWithdraw;. Beware that the original contract will deduct all withdrawn Ether at this point, which leads to the overflow of balances[msg.sender], if SafeMath was used, the reentrancy could be avoided by throwing an exception.
The outcome is that attacker only used 1 Ether to withdraw all the Ether in the original contract.
1. Use the built-in transfer() function in Solidity to send Ether to external addresses if possible.
Transfer() function only sends 2300 gas when executed, which is not enough for calling another contract (contract which sends reentrancy). Using transfer() function to rewrite the withdrawFunds of original contract as follows:
2. Make sure that the changes of state variables happen before Ether is sent (or any other external calls), which is the checks-effects-interactions recommended by Solidity.
3. Use mutexes: Add a state variable to lock the contract during execution to prevent reentrancy.
Following the incident review, reentrancy attack was the main attack method in the DAO incident and it led to the hard fork of Ethereum Classic. Please refer to Phil Daian’s article for more detailed analysis of the original case.
Transaction-Ordering Dependence Attack
Similar with most blockchain, Ethereum nodes pool transactions and form a block. Once the block producers solved consensus mechanism (the ETHASH PoW of Ethereum), these transactions are considered valid. The miners who solve this block will also choose which transactions from the pool will be included in this block, which is usually decided by gasPrice. In here lies a potential attack vector: Attackers can inspect if the transaction pool contains solutions to problems, alter or revoke their own access or change the state which is undesirable for them. Then attackers can obtain data from this transaction and create a higher prioritized transaction gasPrice to include their own transactions in the block.
Consider the following contract:
This contact contains 1000 Ether, the user who finds and submits the correct answer will get this reward. When a user finds the correct answer, let’s say Ethereum!. He calls solve function and use Ethereum! as parameter. Unfortunately, attackers can observe the answers submitted in transaction pool. They submit a new transaction with much higher gasPrice after seeing this solution and checking its validity. Miner who solves this question will likely accept the attacker’s solution first due to the higher gasPrice. So attacker wins 1000 Ether, and the original question solver will end up getting nothing (there is no Ether left in the contract).
There are two types of users can use this kind of attack. Users (who modify their gasPrice for the transaction) and miner themselves (who can rank the transactions according to their own preference). Obviously, a contract which is easier to get attacked by the first type of users is worse. Because miners can only initiate attack when solving one block, which is almost impossible for a single miner who is aiming at one certain block. Here are some counter-measures for these two types of attacks.
The first counter measure is to create some limit conditions, namely the gasPrice limit. This measure can prevent user from increasing gasPrice and obtain the higher priority of getting transaction accepted. But this measure can only mitigate the risk of the first type of attack. In this situation, miners can still attack since they can rank the transactions no matter how much the gasPrice is.
The more reliable measure is to use commit-reveal. This measure regulates users to use hidden information (normally a hash) to send transactions. When a transaction is included in the block, user will send a transaction revealing the data was sent (the reveal phase). This measure prevents both miners and users from frontrunning transactions as they cannot possibly know the contents of the transaction. This measure, on the other hand, cannot conceal the transaction value (which in some cases is the valuable information that needs to be concealed). For example, ENS smart contract allows users to send transactions of which commitment data includes the Ether amount they are willing to spend. Then user can send transactions of arbitrary value. During the revealing phase, users are refunded the difference between the amount sent in the transaction and the amount they are willing to spend.
Learn from Mistakes
The DAO incident was a huge impact on blockchain industry and the loss of it make a lot of investors suffer. Developers should be careful with the followings to prevent this kind of attack from happening:
1. Refer to the official instructions in Solidity and other developing languages about the build-in functions and models, if there are any specifications, walk within the lines.
2. Think over about the exceptions which may happen to state variables, those which have such potential risks should be locked.
3. Comprehensive usage of gasPrice limit and commit-reveal to make sure transaction information shows at a safe timing.
About Chengdu LianAn Technology
Chengdu LianAn Technology Co. Ltd. is headquartered in Chengdu and focuses on blockchain security field. Founded by Prof. Xia Yang and Prof. Wensheng Guo of UESTC, LianAn Tech’s core team members consist of more than 30 associate professors, postdoctoral students, doctors and masters with experience of studying at overseas leading universities and laboratories (CSDS, Yale, and UCLA) as well as industry elite from Alibaba Huawei, and other famous enterprises. Using formal verification as its core technology, this team has been providing years of services for security critical systems in aerospace, military and other fields. Chengdu LianAn Technology Co. Ltd. is the one and only company in China that applies this technology to blockchain security field.
As the only blockchain security company obtained strategic equity investment from Fenbushi Capital, Chengdu LianAn Technology has signed strategic partnership agreements with well-known corporations such as Huobi, OKEX, LBank, CoinBene, Kucoin, CoinMex, Becent, JBEX, ONT, Scry, CareerOn, loTeX, DALICHAIN, Bytom, Bubi Blockchain, YUNPHANT, and BiXiaoBai. In addition, it has built partnership with Inria France, the top formal verification team in the world. LianAn Technology is on the “2018 China Blockchain Industry White Paper” issued by the Ministry of Industry and Information Technology and has also been selected for the smart contract security audit recommendation list on Etherscan.
Telegram Chinese Group：https://t.me/joinchat/IRgNDA4iCF0Rs92sg5qoVg
Telegram English group： https://t.me/joinchat/IRgNDBBpCon-695ATmbA4w