What’s wrong with ERC20Permit?
Introduction
Recently, our partners from Lido, in collaboration with Immunefi, were alerted to a vulnerability classified as a WARNING. This vulnerability, under specific conditions, can significantly disrupt the user experience (UX) during the processing of a withdrawal request. This revelation is particularly concerning because it not only affects Lido but also extends to many of the largest DeFi protocols and their major forks, indicating a widespread potential risk in the DeFi ecosystem.
What makes this situation especially intriguing is the discovery by our security researchers that this vulnerability is not isolated to a single protocol or implementation but is, in fact, a systemic issue that many top DeFi platforms are exposed to. The commonality of the vulnerability across various protocols suggests a fundamental oversight in the standard practices adopted by DeFi developers.
We are strongly concerned that all protocols that use ERC-20 Permit should figure out what is this issue about and explore the ways of preventing it. Let’s figure out what the problem is here and how we can prevent them in the future!
Vulnerability description
Let’s start with the core of the problem. Let’s have a look at a specific example of Lido code:
Let’s look at the code below. These are the methods that Lido uses to process withdrawal requests using the Permit mechanism in the context of ERC-20 tokens:
function requestWithdrawalsWithPermit(uint256[] calldata _amounts, address _owner, PermitInput calldata _permit)
external returns (uint256[] memory requestIds) {
STETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s);
return requestWithdrawals(_amounts, _owner);
}
function requestWithdrawalsWstETHWithPermit(uint256[] calldata _amounts, address _owner, PermitInput calldata _permit)
external returns (uint256[] memory requestIds) {
WSTETH.permit(msg.sender, address(this), _permit.value, _permit.deadline, _permit.v, _permit.r, _permit.s);
return requestWithdrawalsWstETH(_amounts, _owner);
}
These functions are designed to streamline transactions by combining token approval and withdrawal request operations into a single call.
ERC20Permit (stETH
and wstETH
inherit from this) uses the nonces mapping for replay protection. Once a signature is verified and approved, the nonce increases, invalidating the same signature being replayed.
Both the above methods expect the user to sign their tokens with the WithdrawlQueue
contract address as spender and submit the transaction to the chain with the signature uint8 v
, bytes32 r
, bytes32 s
as part of the arguments.
When the user transaction is in the mempool, an attacker can take this signature, call the token.permit
function on the token themselves with the following arguments:
address owner = victim address
address spender = WithdrawlQueue contract address
uint256 value = _permit.value copied from the still pending transaction
uint256 deadline = _permit.deadline copied from the still pending transaction
uint8 v = _permit.v copied from the still pending transaction
bytes32 r = _permit.r copied from the still pending transaction
bytes32 s = _permit.s copied from the still pending transaction
Since this is a valid signature, the token accepts it and increases the nonce. This makes the user’s transaction fail whenever it gets mined, which of course breaks the UX.
Mitigation strategies
Now, let’s have a look on what is happening inside the ERC-2612.
ERC-2612 itself makes the following mitigation recommendations:
The key information here is contained in the following paragraph
Signed Permit messages are censorable. The relaying party can always choose to not submit the Permit after having received it, withholding the option to submit it. The deadline parameter is one mitigation to this. If the signing party holds ETH they can also just submit the Permit themselves, which can render previously signed Permits invalid.
Inattentive reading of this paragraph led to the omission of the fact that the signature can become invalid even after submitting a Permit by any person who saw the transaction in the mempool.
What makes it more noticeable is a EIP-712 Security Considerations:
As you can see, this subarticle takes into account the possibility of front-running and recommends to implement all the necessary checks according to the protocol logic. However, most of vulnerable contracts just decided to reject front-runned TXs.
The only way to mitigate that is to implement multiple custom preventing mechanisms or, at any rate, add some fixes according to provided specifications.
Conclusion
In concluding this article, it becomes evident that meticulous attention to detail in reading specifications and implementing secure practices is not just advisable but essential in the realm of blockchain technology and smart contract development. This issue serves as a compelling case study underscoring this necessity.
References
(1) - Potential withdrawal request griefing vector via `permit` front-running #803 | Lido-dao
(2) - ERC-2612 specification | by Martin Lundfall in Ethereum EIPs
(3) - EIP-712 specification | “Security Considerations” subarticle
Contents
YOU MAY ALSO LIKE
Review of Open Research Problems in Rollup Design
Research
Uncover unresolved challenges in rollup architecture design, shaping the future of Layer 2 solutions in blockchain technology.
Securing Layer 2: Unique Security Concerns and Mitigation Strategies
Research
Explore the unique security challenges of Layer 2 blockchain solutions and learn about OXORIO's specialized strategies for mitigating risks in smart contracts and zk audits.
Have a question?
Stay Connected with OXORIO
We're here to help and guide you through any inquiries you might have about blockchain security and audits.