The vulnerability exploited by Curve may have opened up new ideas for hackers.

The Curve vulnerability may inspire hackers with new ideas.

Author: Jaleel, BlockBeats

With the occurrence of a vulnerability exploit, the DeFi industry has fallen into chaos. Curve Finance, the giant of the DeFi industry, has become a serious target of “attacks”, with multiple stablecoin pools such as alETH/msETH/pETH on the verge of collapse. According to incomplete statistics, this vulnerability exploit event has caused a cumulative loss of $52 million for Alchemix, JPEG’d, MetronomeDAO, deBridge, Ellipsis, and CRV/ETH pools, severely shaking the confidence of the entire market.

Vyper versions 0.2.15, 0.2.16, and 0.3.0 had a reentrancy lock failure, and the version recommended on the Vyper official documentation installation interface was also incorrect. Other projects using the Vyper compiler have also hurriedly conducted self-checks in an attempt to ensure they will not become the next victims. As the source of the vulnerability exploit event is gradually revealed, the market is gradually realizing that this crisis not only signifies an ordinary hacker vulnerability exploit event, but also exposes the potential huge risks of the entire DeFi industry to the underlying stack.

Compared to the past, the number of hacker incidents in recent times has been decreasing, which is closely related to the prosperity of the market. During the DeFi summer and NFT summer periods, new billion-dollar protocols were launched every week, but now the market has shrunk significantly. At the same time, hackers are also gradually losing market opportunities for vulnerability exploitation or creating large-scale attack events, which means that hackers need new, undeveloped entry points to explore.

Returning to the “first principles,” hackers have found a perfect entry point on a lower-level compiler to feast on the huge and delicious “cake” in the DeFi market, making lower-level compilers a “smarter” choice for hackers. Regarding this incident and the related issues it exposed, BlockBeats interviewed smart contract developer Box (@BoxMrChen) and BTX researcher Derek (@begas_btxcap).

How did the Curve incident happen?

Stani, the founder of Aave and Lens (@StaniKulechov), expressed his views on the incident on social media: “This is an unfortunate setback for Curve and DeFi. Although DeFi is an open space where contributions can be made, it is difficult to be absolutely correct, and the risks are high. In the case of Curve, they did the right thing at the protocol level.”

The vulnerability exploit event encountered by Curve is one of the oldest and perhaps most common forms of Ethereum smart contract attacks, reentrancy attacks. Reentrancy attacks allow attackers to repeatedly call a function of the smart contract without waiting for the completion of the previous call to that function. In this way, they can continuously exploit the vulnerability to make withdrawals until the funds in the victim contract are exhausted.

Reentrancy lock and CEI principle

Let’s take a simple example to illustrate a reentrancy attack: a bank has a total of $100,000 in cash. However, this bank has a major vulnerability. Whenever people withdraw money, the bank staff does not immediately update the account balance but waits until the end of the day to check and update. At this point, someone discovers this vulnerability and opens an account at the bank, deposits $1,000, and then withdraws $1,000, and after 5 minutes, withdraws another $1,000. Since the bank does not update the balance in real-time, the system will consider that the account still has $1,000 before performing the check and update. By repeating the operation, the user eventually withdraws all $100,000 in cash from the bank. It is not until the end of the day that the bank realizes that this vulnerability has been exploited.

The complexity of a reentrancy attack lies in its exploitation of the characteristics of mutual invocation between contracts and the logical vulnerabilities of the contracts themselves, achieving fraudulent behavior by deliberately triggering exceptions and fallback functions. Attackers can repeatedly exploit the logical vulnerabilities of contracts to steal funds. The solution to prevent reentrancy attacks is also common, which is to pre-set a targeted special code content for protection, using such a protection mechanism to ensure fund security, which is called a reentrancy lock.

Solidity has set a “CEI principle” (Check Effects Interactions) for smart contract programming, which can effectively protect functions from reentrancy attacks. The content of the CEI principle includes:

1. The calling order of function components should be: first check, then affect state variables, and finally interact with external entities.

2. Before interacting with external entities, all state variables should be updated first. This is called “optimistic accounting”, which means writing the effects before the interaction actually occurs.

3. Checks should be performed at the beginning of the function to ensure that the calling entity has the permission to call the function.

4. State variables should be updated before any external calls to prevent reentrancy attacks.

5. Even trusted external entities should also follow the CEI pattern, as they may transfer control flow to malicious third parties.

According to the documentation, the CEI principle helps to limit the attack surface of contracts, especially to prevent reentrancy attacks. The CEI principle can be easily applied by simply following the order of functional code without changing any logic. The well-known The DAO vulnerability that caused the Ethereum fork was also exploited by ignoring the “CEI principle” and successfully implementing a reentrancy attack, causing devastating consequences.

However, the attacked Curve pool did not follow this CEI principle because Curve used the Vyper compiler. The Vyper code vulnerability as a compiler caused the reentrancy lock to fail, enabling the hacker’s reentrancy attack to succeed.

Most people are familiar with Solidity, but Solidity is not the only language for creating smart contracts. The popular alternative to Solidity is currently Vyper. Although Vyper has less functionality and popularity than Solidity, it is an ideal choice for developers familiar with Python because Vyper can transpile Python-like code into Ethereum smart contract programming language.

According to information from GitHub, the top contributor to the Vyper GitHub code repository is also a developer of Curve. This also explains why Curve uses Vyper instead of Solidity.

Why did the Vyper reentrancy lock fail?

So, what exactly went wrong with Vyper in this attack? Why did the reentrancy lock fail? Was it due to lack of testing? BlockBeats interviewed smart contract developer Box 826.eth (@BoxMrChen), who revealed that the Vyper reentrancy lock did undergo test cases. However, the reason for the failure was that the test cases were result-oriented, meaning the test cases were also incorrect.

In short, the main reason for the failure of the Vyper reentrancy lock is that the person writing the test cases wrote them based on the results, without considering why the slot would inexplicably skip 1.

In the Vyper code shared by Box, the problem is evident. When the lock name appears for the second time, the number of storage slots is overwritten, which means that in the ret, the slot for the first lock acquisition is 0, but after another function uses the lock, the lock’s slot is incremented. Compiling with the wrong slot causes the reentrancy lock to fail.

On the left is the vulnerable code, and on the right is the code after being fixed.

“The expected incorrect test results naturally cannot detect errors. For example, let’s take a simple math problem. 1 + 1 = 2, but the given correct answer is incorrect and says 1 + 1 = 3. If a student answers incorrectly and says 1 + 1 = 3, but it matches the given correct answer, the program naturally cannot detect that the test result is incorrect.” Box said this in an interview with BlockBeats.

The two-year-long “Sword of Damocles”

In the first recorded reentrancy attack incident, the attacker of the WETH Attack, Wei’s Attack, intentionally created the attack to make developers pay attention to the possibility of reentrancy attacks, with the goal of protecting more projects from the possibility of reentrancy attacks. In the context of smart contracts, developers should use different triggering mechanisms, such as calling a state-changing function to implement protection. This requires developers to fully consider possible attack scenarios and take appropriate preventive measures when designing contracts.

To gain a deeper understanding of the Vyper editor, BlockBeats interviewed BTX researcher Derek (@begas_btxcap). He said that for developers familiar with Python, Vyper is a more ideal choice than Solidity, with a more comfortable UI and faster learning curve. However, it is evident that some versions of the Vyper editor code have not undergone reliable third-party audits. Some audits may even be done by the developers themselves. “This wouldn’t happen in the traditional IT industry because when a new language comes out, countless audit companies will go all out to find the vulnerabilities.”

Not to mention that it allowed such an error to exist for two years.

Vyper contributor fubuloubu also stated that the compiler has not been reviewed or audited as much as people imagine. Most compilers undergo significant and frequent changes, which makes auditing difficult. Even with a complete codebase audit, the more versions added afterwards, the more outdated it becomes. Auditing the compiler is not a good way because it makes more sense to audit the final product generated by end users using the tool (i.e., the original EVM code).

All of this points to the final issue: incentives. In other words, no one has the motivation to search for critical vulnerabilities in the compiler, especially in older versions. fubuloubu previously proposed a suggestion to improve Vyper by adding a user-sponsored bounty program, but it was not approved.

Hackers are returning to “first principles.”

For developers of protocols and projects, this is another vivid example of secure contract development practices. But most importantly, the Curve incident serves as a warning to all of us that the security issues of the underlying compiler have been seriously overlooked. Hackers who return to “first principles” have found a perfect entry point on a lower-level compiler.

Afterwards, Stani, the founder of Aave and Lens, also posted a lengthy article on social media expressing his thoughts: This attack on Curve means that DeFi risks have always involved the entire underlying stack, programming languages, EVM, etc. This warns us to be more cautious and sensitive, especially when using custom EVMs and application chains in the future.

Attacks from a lower level

It is difficult to discover compiler vulnerabilities simply by auditing the contract’s source code logic. Studying the differences between versions is also a major undertaking. It is necessary to combine specific compiler versions with specific code patterns for analysis to determine if a smart contract is affected by compiler vulnerabilities.

“Currently, there are only two compilers that are optimal. Vyper’s codebase is smaller, easier to read, and has fewer changes in its history of analysis, which may be why hackers started here. Solidity’s codebase is somewhat larger.” Fubuloubu even suspects that state-sponsored hackers may have been involved in this Curve attack: “It takes weeks to months to find this vulnerability, considering the resources invested, it may have been done by a small group or team.”

As the most widely used compiled language in the cryptocurrency industry, the security of Solidity is a major concern for users. After all, if the reentrancy lock failure issue occurred in the Solidity compiler, the entire history of the DeFi industry may be rewritten.

According to security alerts regularly released by the Solidity development team, security vulnerabilities have also existed in multiple versions of the Solidity compiler.

The most recent compiler error record was on June 26th, when an error was found in the old code generation pipeline of the Solidity compiler while investigating security reports related to using the abi.decode with side-effecting ternary expressions as type arguments. The old code generator did not evaluate complex expressions such as assignments, function calls, or conditions, while accessing the .selector. This caused side effects of such expressions not to be executed, so the behavior of contracts compiled using the old pipeline may be incorrect.

We can also see a file in the Solidity GitHub repository that lists some known security-related bugs in the Solidity compiler. This list can be traced back to version 0.3.0, and bugs that only exist before this version are not included. There is also another file, bugs_by_version.json, which can be used to query which bugs will affect a specific compiler version.

Luckily, it is precisely because of the widespread use of the Solidity language and the assistance of the Ethereum Foundation behind it that many existing issues have been identified and addressed by projects and protocols during deployment. Therefore, Solidity has completed modifications and improvements faster than Vyper, which makes Solidity more standardized and secure from this perspective.

In order to help Solidity developers carry out better testing and prevent the same incidents from happening again, SunSec, co-founder of UnitasProtocol (@1nf0s3cpt), released a Solidity security testing guide called DeFiVulnLabs, which supports 47 vulnerabilities. The guide includes vulnerability descriptions, scenarios, defenses, vulnerable code, mitigation measures, and how to test.

How to avoid underlying attacks as much as possible?

In this Curve incident, Box believes that the lesson for all developers is: do not be tempted to choose immature solutions following technological trends; do not approve your own code without writing test cases (even on several problematic versions of Vyper, the test cases themselves were incorrect); never self-approve your own code; some vulnerabilities may not be discovered for years; being non-upgradable is arrogance towards oneself and disregard for others.

Usually, developers may not think there are any pitfalls here and simply choose a version to compile, which may overlook the risks brought by differences between versions. Even minor version upgrades can introduce significant changes, which is especially important when developing decentralized applications.

This Curve incident serves as a warning to developers: use newer versions of compiler languages. It is crucial to keep the codebase, applications, and operating systems up to date, and to build comprehensive security defenses. Although new versions may also introduce new security issues, known security issues are usually fewer than older versions. Of course, it is also important to pay attention to community and official version update announcements. Understand the changes brought by each version and update your codebase and runtime environment as needed. Taking these measures may greatly reduce security incidents caused by compiler errors.

In addition, it is important to improve the unit test cases of the code. Most compiler-level errors result in inconsistent code execution results, which is difficult to discover through code review alone, but can be exposed through testing. Increasing code coverage helps to avoid these types of issues. Also, try to avoid using complex language features such as inline assembly and multi-dimensional array encoding and decoding unless there is a specific requirement. Most historical Solidity language vulnerabilities are related to these advanced features. Unless there is a special need, developers should avoid using experimental language features just to show off their skills.

For protocol layers and security personnel, when conducting code audits, the risks that compiler versions may bring should not be overlooked. It can be foreseen that hackers have already opened up new ideas, and in the future, there will be more incidents of exploiting lower-level vulnerabilities. At the same time, as a lower-level infrastructure, the underlying stack, programming languages, EVM, and others need to be audited properly. The market for auditing companies will become larger in the future, and the market for white-hat bug bounties will also grow. The Vyper team also plans to start a bug bounty program for auditing after the situation is officially resolved.

Of course, we don’t need to panic excessively about the risks that exist in the underlying infrastructure. Currently, most compiler bugs are triggered only under specific code patterns, and the actual impact needs to be assessed based on the project situation. Regularly upgrading compiler versions and conducting thorough unit testing can help prevent risks.