ERC-2771 Delegatecall Vulnerability
Make sure your contracts are not affected by the recently disclosed vulnerability
Last updated
Make sure your contracts are not affected by the recently disclosed vulnerability
Last updated
Please read the following resources from OpenZeppelin and ThirdWeb explaining the vulnerability:
ERC-2771 is a standard enabling contracts to authenticate users during transaction relaying. Before delving into the security risks of its implementation, it is crucial to understand the mechanics of the ERC-2771 flow.
User Request Signing
The user signs their request and incorporates this signature into the payload.
Relay Contract Verification
The relay contract validates the signature and appends the user's 20-byte address to the end of the calldata
.
Target Contract Decoding
The target contract decodes the user address by extracting the last 20 bytes from the calldata
, but only when msg.sender
is the relay contract, known as the Trusted Forwarder.
Decoding is done using assembly for efficiency, as shown in the following code snippet:
delegatecall
Context Preservation in delegatecall
When Contract A invokes Contract B using delegatecall()
, msg.sender
in Contract B remains the original caller, as delegatecall()
preserves the caller's context.
Address Extraction in ERC-2771
As outlined above, extracting the original user address involves verifying that msg.sender
is the Trusted Forwarder, then retrieving the user address from the final 20 bytes of callData
.
ERC-2771 Relayer Specifics
If an ERC-2771 Relayer is employed and the target method uses delegatecall()
to its own address (address(this).delegatecall(...)
), the Trusted Forwarder check will always pass, as msg.sender
will consistently be the Gelato Relay Contract.
In scenarios where the target method modifies the calldata
, it becomes uncertain whether the last 20 bytes accurately represent the original user when _msgSender()
is invoked.
🚨 If you're implementing
delegatecall()
in conjunction with ERC-2771, please reach out to us for assistance. We'll help ensure that your implementation is robust and secure.
The vulnerability described arises when all three of the following conditions are met in a smart contract. It's crucial to avoid these conditions concurrently.
Avoid the following conditions in the same smart contract:
Implementation of ERC2771Context or assumptions on data from the trusted forwarder: the contract either implements ERC2771Context or operates under the assumption that data from the trusted forwarder will be appended to and subsequently extracted from the calldata
.
Use of delegatecall to Self-Contract: the contract uses delegatecall
to call itself, typically indicated by address(this).delegatecall(...)
.
Calldata manipulation: situations involving the manipulation of calldata
, common in functions like multicall
.
multicall
in combination with ERC-2771The vulnerability is evident in a typical multicall
function, structured as follows:
Vulnerability Mechanism
Within the loop, delegateCall()
is executed, targeting the contract itself (address(this).delegatecall(data[i]
).
When _msgSender()
is evaluated within this call, it does not return the original user who signed the transaction. Instead, it yields the last 20 bytes of data[i]
.
Potential for Exploitation
A malicious actor could exploit this by appending a victim's address at the end of data[i]
.
As a result, _msgSender()
would erroneously identify the victim's address as the validated user who signed the transaction, leading to potential security breaches.
Key Points
Context Determination: The context is derived by comparing msg.sender
and _msgSender()
. If they match, no additional context is appended. Otherwise, the last 20 bytes of msg.data
are used.
Secure Delegatecall: By appending the context to each data[i]
before the delegatecall
, the function ensures that the original sender's address is correctly interpreted in subsequent calls.
Robust Error Handling: The use of require(success)
after each delegatecall
ensures that any call that fails will halt the execution, maintaining the integrity of the operation.
To securely implement multicall
in conjunction with ERC-2771, it is recommended to manually append the context to each data[i]
, as outlined in . The approach involves the following steps: