GoPlus Security: Analysis of Curve Attack Caused by Vulnerability in Vyper Compiler Version 0.2.15
Author: GoPlus Security
Background
Recently, some stable pools (alETH/msETH/pETH) written using Vyper version 0.2.15 suffered from reentrancy attacks. This was due to a reentrancy lock failure caused by an implementation error in that version of the compiler. This vulnerability resulted in a loss of approximately 50 million USD. This article will analyze the root cause of the vulnerability and the principles of the attack. Additionally, optimization suggestions will be provided to prevent similar incidents from occurring in the future.
Root Cause Analysis
The root of the vulnerability lies in the compiler's incorrect implementation of the @nonreentrant(\<str>) decorator semantics.
Correct Semantic Description
@nonreentrant(\<str>) is used to restrict function reentrancy, and its semantics are similar to mutex locks in multithreading:
Single Function Decoration: When a function is decorated with this decorator, multiple calls to it cannot be executed simultaneously.
Multiple Function Decoration: When multiple functions use this decorator and the reentrancy restriction key is the same (same string value), their multiple calls still cannot be executed simultaneously.
A correct implementation would set a boolean value for each reentrancy restriction key, where 0 represents that a function is executing, and 1 represents that no function is executing. Multiple functions restricted by this key would synchronize based on this variable (acquire key -1, release key +1, and check if the value is 1 when attempting to acquire the key).
Actual Semantic Implementation
In Vyper compiler version 0.2.15 and earlier, the implementation of this semantics was incorrect. The erroneous code is shown below.
Analysis The storage_slot in the code corresponds to the synchronization variable mentioned above. The error lies in its value range; this variable starts at 0, and each time the interpreter processes a function declaration with @nonreentrant, it increments the synchronization variable by +1, which is redundant. When the code meets the multiple function decoration scenario mentioned above, the value range of the synchronization variable no longer satisfies the boolean value, which does not conform to the correct semantics (i.e., there is a lock failure). In fact, the TODO comment in the code already reflects the developer's concerns.
Fix In the patch, it checks whether the key has been created; if so, it no longer increments by +1.
Result Some contracts were attacked because the actual implementation of this decorator did not match expectations, even though these contracts were theoretically correct. For example, the case introduced next.
Contract Attack Case
All contracts that meet the multiple function decoration criteria mentioned above are potentially vulnerable to attack. For instance, one attacked contract has five functions all decorated with the same key @nonreentrant("lock"), allowing for mutual reentrancy.
- add_liquidity
- remove_liquidity
- exchange
- removeliquidityimbalance
- removeliquidityone_coin
The hacker's attack exploited the first two functions. They are used to add and remove assets from the liquidity pool, from which liquidity providers can earn transaction fees and other rewards. The attack process is similar to a typical reentrancy attack:
- The first call to add_liquidity deposits assets.
- The call to remove_liquidity removes these assets.
Here, removeliquidity will return ETH to the hacker through an external call. At this point, the hacker completes the reentrancy by calling addliquidity within the fallback function.
Upon reentering addliquidity, since removeliquidity has not yet reduced the total token supply total_supply, the calculation for the number of tokens minted for the hacker exceeds the expected amount.
remove_liquidity()
add_liquidity()
Solutions and Future Development
In response to this incident, we have some thoughts on future Web3 security development solutions:
1. Responsibility and Sensitivity of Protocol Developers
Protocol developers need to actively pay attention to updates and security issues in the core supply chain, timely obtain relevant information, and increase their understanding of underlying implementations.
2. Introduction of DevSecOps
How to better integrate security testing and validation tools into the contract development process, whether static analysis or dynamic fuzzing, embedded in the SDLC (Software Development Life Cycle), so that some issues can be exposed and resolved earlier.
3. Introduction of Software Composition Analysis (SCA) Tools
We noticed that after the Vyper vulnerability was exposed, several attacked protocol teams had about half a day to a day to take remedial measures, but unfortunately, they ultimately failed to save the stolen assets. We believe that for such supply chain issues, a comprehensive supply chain management platform should be introduced, similar to what we commonly refer to as artifact repository management in Web2, which can quickly alert and provide upgrade solutions when supply chain issues arise.
4. Innovation in Transaction Underlying Paradigms
At the infrastructure level, consider implementing automated protections at runtime, such as dynamic security checks, or automatically executing protective measures when performing critical operations. We are also pleased to see many trends in this area, such as Uniswap V4, which greatly enhances the extensibility of transaction functions by introducing Hooks, allowing developers to provide protections at various stages of transactions to enhance security. Similarly, Artela enables developers to achieve multi-stage extensions at the Runtime level, naturally addressing the major issue of reentrancy, as detailed in https://medium.com/@artela_chinese/.
Conclusion
The complexity and difficulty of smart contract development make the occurrence of vulnerabilities almost inevitable. However, by better understanding and utilizing existing tools, continuously engaging with and participating in open-source community activities, and optimizing and improving infrastructure, we can reduce the risk of vulnerabilities and enhance the overall security and robustness of the system. The future of blockchain security requires not only the joint efforts of compilers and developers but also relies on the support and promotion of advanced tools such as static analysis and dynamic fuzzing. Similarly, it requires innovators to break the constraints and propose better underlying transaction models and solutions, making the entire Web3 world safer.