Postmortem on the Primitive Finance Whitehack of February 21st, 2021
No user funds lost. A critical vulnerability was discovered in Primitive Finance smart contracts that would allow all funds to be stolen from Primitive Finance users according to their token allowances. There were 88 potential victims, most with infinite approvals for important tokens, such as WETH or DAI, and with overall holdings of well over $10M. $1.3M of these funds were vulnerable at the same time, the rest only when/if converted to WETH, DAI, or other approved tokens.
Risk to user funds was eliminated via a pre-emptive whitehack by the Primitive Finance team and its war room.
Timeline of Events
15:30 UTC Feb 20: Dedaub team confirms critical vulnerability by exploiting a contract in a test environment.
16:30 UTC Feb 20: Yannis Smaragdakis at Dedaub discloses the critical vulnerability to Mitchell Amador at Immunefi.
17:00 UTC Feb 20: Immunefi team confirms the vulnerability.
17:40 UTC Feb 20: Primitive Finance confirms receipt of vulnerability from Immunefi.
17:45 UTC Feb 20: Primitive Finance engages Emiliano (ReviewsDAO).
17:50 UTC Feb 20: Primitive Finance war room created with Primitive Finance, Dedaub, ReviewsDAO, and Immunefi teams.
17:53 UTC Feb 20: War room assembles, begins preparing whitehat hack and operations.
18:15 UTC Feb 20: Primitive Frontend updated with all buttons calibrated to reset approvals to 0 wei, to prevent new wallets becoming vulnerable.
20:45 UTC Feb 20: Scope of the vulnerable wallets and funds at risk confirmed, with the help of Jon Itzler’s Dune Analytics queries.
17:17 UTC Feb 21: Whitehack contracts prepared, code review begins.
19:45 UTC Feb 21: Alice Henshaw from Open Zeppelin joins war room to offer additional support.
22:18 UTC Feb 21: War room re-assembles to initiate whitehack.
00:19 UTC Feb 22: Staging complete, attack is ready.
00:41 UTC Feb 22: Pre-emptive reach out to known address holders to reset allowances, exposed funds reduce by 35%.
01:06 UTC Feb 22: Primitive Team executes first whitehat attack, rescuing first wallet.
01:12 UTC Feb 22: Primitive Team executes second whitehat attack, rescuing second wallet.
01:14 UTC Feb 22: Primitive Frontend updated with emergency reset page. Announcement made in discord.
01:16 UTC Feb 22: Primitive Team executes third whitehat attack, rescuing third wallet.
03:14 UTC Feb 22: Primitive Team safely returns all rescued funds to their owners.
04:00 UTC Feb 22: Confirmed 98% of originally exposed funds have been saved.
The Primitive Connector contract code contains entry point flashMintShortOptionsThenSwap. This entry point allows the minting of option tokens. The entry point is not directly publicly callable: the downstream code checks that the external caller of the contract is the contract itself. In normal use, this condition is satisfied when the function is called by a generalized dispatcher, activated after a Uniswap v2 flash-swap operation.
To reach this code with unrestricted arguments, an untrusted caller can ask for a Uniswap flash-swap with parameters much like the ones in the legitimate code, make it execute the Primitive Connector contract with the swapped funds, but supply the attacker’s parameters. The Primitive Connector code (uniswapV2Call) does not check the initiator of the flash-swap operation, only that the callback indeed comes from Uniswap.
In technical and financial terms, the essence of the attack is:
- Find victims, i.e., contracts that have “approved” the vulnerable contract for their tokens. Let’s call these “real” tokens.
- Create a fake, worthless token.
- Create a malicious, worthless option token that is based on the real token (“underlying”) and the above fake token (“redeem”).
- Create a Uniswap liquidity pool, allowing trading the worthless “redeem” token for the real, “underlying” token. This pool can be financed via a flash loan, since it will be restored to the original balances at the end of the attack.
- Start a flash swap that asks for the victim’s amount of real tokens and then invokes the entry-point function, flashMintShortOptionsThenSwap, with the right parameters. The vulnerable contract will start executing with real tokens in hand.
- Pass the real tokens to the malicious option contract.
- Ask it to mint malicious-option tokens. The malicious option then transfers the real tokens to the attacker.
- Give the option tokens to the victim account.
- Settle the flash swap balance by paying out of the victim’s funds.
The result is that the victim’s funds are transferred to the fake option contract, while leaving the Uniswap pool with the same amount of (real) tokens. The pool can now be dismantled to recover its underlying tokens that were originally put in. (E.g., to repay the flash loan, in the implemented version of the attack.) The net effect is that the attacker is left with the victim’s real tokens.
How we fixed the vulnerability
Since our v1 contracts are not upgradeable or pausable, we had two courses of action:
1. Whitehat hack our own smart contracts to safeguard user funds.
2. Get all Primitive Finance users to reset their allowance to 0 Wei.
We concluded these two methods would be most effective in safeguarding user funds, so we did both. These methods proved successful, with 98% of all funds presently secured.
What’s next for Primitive Finance
Our immediate next steps to secure the protocol and prevent future events like this are:
- Ensure users successfully reset their allowances via continuous monitoring and outreach; 98% of funds secured, but there are still outstanding allowances.
- Terminate active use of the vulnerable Primitive Finance “Connector” contract. Notify etherscan of the hacked contracts and ask them to mark them as vulnerable.
- Follow a strict approval policy when building user interfaces: No infinite approvals.
- Update frontend to use permit for all tokens which support it.
- Add pausability to the smart contracts.
- Update the frontend with new tools for users to interact with the option markets, until a new “Connector” contract is deployed.
This event was the first real security challenge at Primitive Finance. Thanks to the support of the community no user funds were lost and much real experience acquired. While we hope to never again have a similar event, we now know how to respond and whom to call on. We are confident in our ability to weather all future storms.
We are deeply thankful to Yannis Smaragdakis and Neville Grech at Dedaub, who responsibly disclosed the vulnerability through immunefi.com, developed the whitehat hack that saved user funds, and provided a list of all affected users and balances. They will receive a bug bounty for their efforts in saving more than $1m in user funds.
Warm thanks to Mitchell Amador and Duncan Townsend at Immunefi, who after receiving the disclosure and validating the exploit immediately alerted Primitive Finance and war roomed through the weekend with us to a successful whitehack.
We are also thankful for Emiliano Bonassi at ReviewsDAO. He generalized the hack further, adding support for flash-loans and packaging it to be tested and executed safely. More importantly, Emiliano led and coordinated the war room emergency response. Emiliano’s expertise in setting up and working through the test and execution environments proved pivotal to the successful operation, and we are most grateful for his support.
The Primitive Finance Team