Fei Protocol Flashloan Vulnerability Postmortem

3 years ago 198
BOOK THIS SPACE FOR AD
ARTICLE AD

Immunefi

Summary

Whitehat Alexander Schlindwein, known for receiving the largest ever bug bounty payout after finding a critical bug in ArmorFi, discovered a critical vulnerability in Fei Protocol. Concurrently and independently, Fei Protocol’s expert security team, led by Joey Santoro, also discovered this bug and immediately paused the contract. If this vulnerability had been exploited, it could have drained the protocol of 60,000 ETH via a flash loan attack (limited by the ~1m in ETH available across the ecosystem for flash loans). Alexander Schlindwein reported this bug to Immunefi on May 2. The vulnerability was not exploited. No funds were lost.

Fei Protocol is awarding Alexander Schlindwein an $800,000 bounty, paid in TRIBE. This award is in recognition of Alexander’s contributions to the fix of this vulnerability and as a demonstration of Fei Protocol’s commitment to whitehat hacking and to the broader Ethereum ecosystem. This bounty represents one of the largest awards ever paid out in DeFi history. Fei Protocol has also published a postmortem of the vulnerability.

Vulnerability Analysis

Fei Protocol is a decentralized, algorithmic stablecoin that maintains the price of Fei at the peg through a variety of methods. One method is through Protocol Controlled Value (PCV). The idea is that the protocol itself controls a substantial amount of the liquidity provider tokens (LP tokens) in the Uniswap V2 pool for the ETH/FEI pair (an LP token represents a pro rata share of each pool of tokens that are deposited).

The overview of the vulnerability is that flash loan-driven market manipulation can drain Fei Protocol of protocol-controlled funds.

The issue is that anyone can call allocate(), which takes the protocol-controlled value (ETH controlled by the protocol, PCV) and puts it into the Uniswap pool at the prevailing market rate (and not the ETH/USD oracle price, as designed). This function cannot be called during the Genesis period, which is now over, and cannot be called during a pause.

Address.isContract and the nonContract modifier are designed to prevent price manipulation of FEI during the allocate operation, but this guard as-written does not work. It can be bypassed if invoked by a contract’s constructor, as we see below.

The exploit can be illustrated in the following steps.

1. Take a flashloan in WETH

2. Dump the ETH into the Uniswap ETH/FEI pool. This makes FEI very expensive and ETH very cheap

3. Call the ETH bonding curve purchase to buy FEI at the protocol-established price of $1.01, even though the FEI market price is now much higher in the Uniswap pool

4. Construct a dummy contract whose only purpose is to invoke allocate from the constructor in order to bypass the nonContract modifier

5. Call allocate (from the constructor of the dummy contract), which eventually invokes addLiquidityETH at the ETH/USD oracle price, but with 100% slippage tolerance. This deposits the PCV (in ETH) into the Uniswap V2 pool. The counterpart quantity of FEI is minted/burned directly by the protocol. Because the market is currently distorted by the flash loan, much more ETH is deposited (relative to FEI) as would be dictated by the prevailing, non-distorted market conditions

6. Take the newly bought FEI and swap it back into the pool. This takes advantage of the excess of ETH deposited in step 5. This gets back the original ETH swapped, and, according to calculations by Alexander, additional gains of up to 60,000 ETH

7. Return flash loan

The correct choice of how much ETH to dump into the Uniswap pool versus how much ETH to purchase on the bonding curve is a complicated optimization problem. Alexander Schlindwein wrote a Python program using GEKKO to choose the optimal split of the available flashloaned funds:

The following PoC exploit was written by Alexander Schlindwein to demonstrate this attack:

Best Practices

It’s best practice to use an oracle and slippage tolerance when interacting with a distributed exchange to avoid this kind of market manipulation attack via a flash loan or sandwich. Without an oracle price enforced by slippage tolerance, there’s no way to know whether the price reported by the Uniswap pool is the price established by prevailing market conditions.

If a protocol does not use an oracle with slippage tolerance and its guard against flash loans happens to be faulty, then flash loan attacks are devastating.

Additionally, checks using extcodesize (such as OpenZeppelin’s Address.isContract) to determine whether a given address is a contract are only sufficient if you intend to include only smart contracts not to exclude them. Address.isContract will return false for some contracts, such as those that are currently being constructed. Currently, Solidity doesn’t provide a future-proof method for testing whether an address is a contract. require(msg.sender == tx.origin) works for the current Berlin EVM, but Vitalik has publicly stated that tx.origin may not continue to be meaningful in future versions.

Vulnerability Fix

Fei Protocol temporarily paused the affected contract, EthBondingCurve.sol, and jointly developed a fix with Alexander Schlindwein and OpenZeppelin. This pause was lifted when it was discovered that a pause in EthUniswapPCVDeposit.sol was sufficient to mitigate the bug until a more permanent fix was developed. After review by OpenZeppelin and Immunefi, Fei is deploying two fixes that would each independently prevent this kind of attack.

The primary fix is that ETH deposited to the protocol through the bonding curve is no longer supplied to the ETH/FEI Uniswap pool. This completely removes the vector used by this attack. Instead, the ETH deposited into the bonding curve is directed to the reserve stabilizer, a mechanism for maintaining the price of FEI below the peg. The reserve stabilizer allows anyone to purchase ETH from the protocol at $0.95, placing a hard floor on the price of FEI.

The second permanent mitigation implemented addresses the slippage parameter in the EthUniswapPCVDeposit contract. This fix sets the slippage of the call to addLiquidityETH to a configurable percentage of the ETH/USD oracle price. The contract UniswapPCVDeposit got a new method, _getMinLiquidity, which computes a configurable ratio from the deposited amount to set the amountTokenMin and amountETHMin arguments to addLiquidityETH. Because the EthUniswapPCVDeposit contract always deposits at the oracle price, this sets the slippage relative to the oracle, not relative to the current market ETH/FEI price. Currently, this slippage is set to 1%. If the market is distorted by more than 1% relative to the oracle price, the deposit/allocation transaction will revert.

This also protects Fei from market manipulation due to a sandwich attack. Merely fixing Address.isContract or the nonContract modifier would not protect against a sandwich attack on its own.

Acknowledgements

We want to thank Fei Protocol’s expert security team for their work in independently discovering the bug alongside whitehat Alexander Schlindwein, who is now one of the most important whitehats in DeFi. Alexander has worked closely with Fei Protocol to build a fix. We’d also like to thank Fei Protocol for hosting a bug bounty with Immunefi.

To report additional vulnerabilities, please see Fei Protocol’s bug bounty program with Immunefi. If you’re interested in protecting your project with a bug bounty, visit the Immunefi services page and fill out the form.

Read Entire Article