tl;dr;*
This week, a Yearn periphery contract used to manage some treasury assets was compromised, resulting in a bricked Gnosis Safe. Yearn used EIP-1271 as a workaround to transfer assets from the bricked multisig to safety.*
No user funds are affected
Yearn has a secondary multisig for limited operations, known as "yTrades", meant to be a fast-acting approver for performing one-off time-sensitive operations like swaps. This multisig was the victim of a complex attack this week which rendered it inoperable, and trapped approximately $1,000,000 worth of assets. At the root of this attack is a lesser-known feature of Safe multisigs known as a "Guard".
In short, a Safe Guard is a custom contract which a multisig can optionally add to their Safe to run on-chain checks both before and after every transaction execution. More information here in the Safe docs.
In 2021, Yearn developed and implemented a custom Safe Guard on the yTrades multisig as part of a system designed to help prevent MEV (that system is outside the scope of this blog post but you can read more about it here).
Importantly, Yearn's implementation included a governance role and an executors whitelist, blocking transaction execution if not on the whitelist.
Due to an oversight, the team failed to set the protocol's main multisig (yChad.eth) as the governance role on the Guard contract. Instead, governance remained set as the original deployer EOA, which was a vanity address generated using the Profanity tool (see here for more details about the Profanity exploit).
An attacker recently discovered this detail and used the exploit to compromise the wallet, taking control and executing a transaction designed to brick the yTrades multisig. By taking over the governance and management roles, the attacker was able to empty the Guard's executor whitelist.
Since the executors list was now empty, all transactions from the multisig fail the Guard checks and therefore revert, rendering yTrades completely inoperable. Since removing the Guard itself requires a successful transaction, that too is now impossible.
See our appendix for some tips on how to safely use a Guard.
After some hours of trying to work around Stealth Safe Guard logic to no avail, we received a tip on the possibility of using EIP-1271 for order creation in CoWSwap.
The gist of it is that, if you previously approved a token on CoWSwap's relayer contract, you can approve a CoWSwap order using off-chain signatures alone - no requirement to publish an on-chain transaction.
Let's walk through the steps:
A CoWSwap EIP-712 Order is created and hashed
The hashed order is added to a Safe's EIP-712 message and hashed
Needed owners sign the Safe's message
All signatures are sorted least to greatest and concatenated
An order in the CoWSwap API is created with the original EIP-712 order plus the concatenated signatures
CoWSwap's API accepts the order and inserts it in the orderbook
Solvers compete in an auction for the best settlement
Order(s) settle on-chain
The real magic in this process: everything happens off-chain until the auction settlement. As you can see here, the CoWSwap settlement contract checks Safe's isValidSignature()
, and as long as the Safe message hash was signed by enough owners, it is accepted.
After seeing the power of EIP-1271, we would like to push for a wider adoption of the standard. Safe's smart contract and website will soon allow message signing through their API. This means that if the protocol you want to interact with supports the standard, after approvals you may be able to interact completely gas-free.
We will continue improving our DAO trades settlement with CoWSwap and evolve our order creation through EIP-1271, and are also planning to add support for EIP-1271 interactions to Yearn's V3 vaults.
We would like to take this opportunity to publicly asking the Safe team to add a remove guard functionality to the safe using EIP-1271. If owners want a guard removed, there should be a way to do it through signatures.
Although the majority of the Safe's tokens were recovered, there remain a few tokens that were not approved on CoWSwap's relayer, and thus are locked in yTrades. We are currently treating the attacker as a white hat, and until 6/25 will give them benefit of the doubt. We have created a bounty contract for the guard owner to take action, which can be found here.
We would like to thank the on-chain sleuths who contacted us to provide information about the attacker, as well the engineers from CoWSwap, Safe and elsewhere who aided our work to create these transactions. Furthermore, thank you to everyone developing infra and tooling for Ethereum—even with tooling this was difficult, and without it this would have been impossible.
IMPORTANT: If you are using a safe guard, notice that:
Guard removal goes through a transaction execution of the safe
Safe can only be upgraded through a tx execution of the safe.
Owners can't execute a transaction from the safe without the guard's blessing
If the guard reverts, the transaction doesn't settle
A broken/compromised guard means a bricked safe.
We repeat...
A broken/compromised guard means a bricked safe.
Looking back, there were a few issues which should have mitigate this attack
Stealth Safe Guard governance was never set to yChad
Stealth Safe Guard had a overrideGuardChecks
setter which skipped the pre-transaction logic
Instead of removing the guard (as we ended up not using it), we simply set overrideGuardChecks = true