Full Report
Curve Finance is a central protocol within the DeFi ecosystem. The protocol was written in the Vyper language because of its gas efficiency. Most people assumed that the exploits were due to a known read-only reentrancy within Curve. However, the more people dove into the issues, the more they realized this ran deeper. It looked like a reentrancy issue in Curve. But, how is this possible? It had been audited multiple times! From investigation, it turns out that Vyper had a compiler bug on their reentrancy protection. Since Curve was relying upon the reentrancy protection working, this undermines the whole security of the protocol. The bug had been in the compiler for over a year. How did the compiler mess this up? According to the here, there is a mismatch in the slots that are being checked for reentrancy. This means that the protection was per function instead of per contract, which is really bad for the protocol. From the commit hash, it appears that a check was missing to see if a reentrancy lock had already been made. This resulted in a lock being made per function, which makes the reentrant lock possible to work around. BlockSec's image shows the change that made the code vulnerable. Initially, it was crazy to me that basics tests did not call this. However, a developer would write a test to call the same function back-to-back. In this case, the protection would have worked. Instead, one function in the contract then another one would have been called in order to test this. Performing the extra test cases and a test suite can pay off! A crazy bug led to the destruction of this. I wonder if people will use Vyper in the future or if they will only use Solidity.
Analysis Summary
# Vulnerability: Vyper Compiler Reentrancy Lock Malfunction
## CVE Details
- **CVE ID**: CVE-2023-39098 (Note: Widely associated with the Curve exploit; also refer to Vyper GHSA-679f-68p2-rh27)
- **CVSS Score**: 7.5 (High)
- **CWE**: CWE-662 (Improper Synchronization), CWE-691 (Insufficient Control Flow Management)
## Affected Systems
- **Products**: Vyper Compiler (language for EVM smart contracts).
- **Versions**: 0.2.15, 0.2.16, and 0.3.0.
- **Configurations**: Smart contracts containing native ETH (using `send` or `call`) and utilizing the `@nonreentrant` decorator across multiple functions.
## Vulnerability Description
The vulnerability stems from a compiler-level bug regarding the allocation of storage slots for reentrancy locks. In the affected versions, the compiler failed to ensure that the reentrancy guard used a consistent global storage slot for the entire contract. Instead, it allocated different slots for different functions.
Technical failure points:
1. **Slot Mismatch**: The reentrancy protection became "per-function" rather than "per-contract."
2. **Lock Oversight**: A check was missing to determine if a reentrancy lock had already been established by a previous function call within the same transaction.
3. **Cross-Function Reentrancy**: An attacker could call a function (e.g., `remove_liquidity`), trigger a fallback via ETH transfer, and then re-enter the contract through a different function (e.g., `add_liquidity`) because the second function checked a different (and therefore empty) storage slot for its lock.
## Exploitation
- **Status**: Exploited in the wild (July 30, 2023). Multiple Curve pools (alETH, msETH, pETH, CRV/ETH) were drained of approximately $69M.
- **Complexity**: High (Requires deep understanding of EVM storage and compiler internals).
- **Attack Vector**: Network (Interacting with smart contract functions).
- **PoC Availability**: Yes, multiple exploit transactions are public on the Ethereum mainnet.
## Impact
- **Confidentiality**: Low (Financial data is public on-chain).
- **Integrity**: High (Attacker can manipulate contract state and LP token prices).
- **Availability**: High (Total drain of liquidity pools).
## Remediation
### Patches
- **Vyper Version 0.3.1**: The bug was inadvertently fixed in this version.
- **Vyper Version 0.3.10+**: Contains explicit fixes and regressions tests for this class of vulnerability.
### Workarounds
- **Contract Migration**: Vulnerable contracts must be redeployed using a patched version of the Vyper compiler.
- **Protocol Withdrawal**: Users were advised to withdraw liquidity from pools identified as using vulnerable compiler versions (e.g., Tricrypto on Arbitrum).
## Detection
- **Indicators of Compromise**: Transactions involving recursive calls between different state-changing functions (e.g., `remove_liquidity` followed immediately by `add_liquidity` in the same trace) where `@nonreentrant` should have prevented the action.
- **Detection Methods**: Security researchers use static analysis tools like **Slither** or manual bytecode inspection to verify if different functions are pointing to the same storage slot for the `nonreentrant` flag.
## References
- Vyper Security Advisory: [https://github.com/vyperlang/vyper/security/advisories/GHSA-679f-68p2-rh27](https://github.com/vyperlang/vyper/security/advisories/GHSA-679f-68p2-rh27)
- Rekt News Report: [https://rekt.news/curve-vyper-rekt/](https://rekt.news/curve-vyper-rekt/)
- Analysis by BlockSec: [https://twitter.com/BlockSecTeam/status/1685694840215785472](https://twitter.com/BlockSecTeam/status/1685694840215785472)