Solidity — The Dangers of Delegatecall
In this 0th entry of the “Solidity Series”, we will be exploring several vulnerabilities that can arise when using the delegatecall function in solidity based smart contracts.
Delegatecall allows a contract to pass the execution of a function to another contract, using the state and context of the calling contract. While delegatecall can be a useful tool for smart contract developers, it also introduces the potential for many bugs and vulnerabilities if used improperly.
In this post, we will examine six different vulnerabilities that can occur when using delegatecall, and provide recommendations for preventing these issues.
The first potential smart contract proxy vulnerability we’ll discuss is the “uninitialized proxy vulnerability”. This occurs when the proxy contract is not properly initialized and the functions in the implementation contract are not called correctly. This can lead to unexpected behavior or errors in the contract’s functionality, which can be exploited by attackers. To prevent this type of vulnerability, smart contract developers should ensure that their proxies are initialized correctly and that the functions in the implementation contract are called properly.
A storage collision happens when the storage slot layout in the implementation contract does not match the storage slot layout in the proxy contract. This causes a problem because the delegatecall in the proxy contract means that the implementation contract is using the proxy contract’s storage, but the variables in the implementation contract determine where that data is stored. If there is a mismatch between the proxy contract storage slots and the implementation contract storage slots, a storage collision can happen. To prevent this type of vulnerability, smart contract developers should ensure that the storage slot layouts in the proxy and implementation contracts match.
Delegatecall to Arbitrary Address
One potential risk when using delegatecall is the possibility of calling an arbitrary address, which can lead to a number of issues. If the implementation contract that delegatecall passes execution to can be an arbitrary contract, it can create substantial problems. For example, an attacker could use delegatecall with selfdestruct to launch a denial-of-service attack against the contract. Additionally, if users have trusted the proxy contract with approve or set an allowance, the arbitrary delegatecall target could be used to steal their funds.
Additionally, the target address may not be home to a contract at all. If the external contract called does not exist, the function will return a value of true.
To prevent these issues, it is important for smart contract developers to ensure that the address that delegatecall transfers execution to is a trusted contract and is not open-ended, allowing a user to specify any address as the delegate. It may also be necessary to perform validation that the target address is indeed a contract — via in-code requires or manual verification.
Function clashing is a result of compiled smart contracts using a 4 byte identifier (derived from the function name’s hash) to identify functions, known as a function selector. Functions with different names can contain identical 4 bytes identifiers when the first 32 bits of their hashes are the same. The compiler will detect when the same 4 byte function selector exists twice in a single contract, but it does not prevent the same 4 byte function selector from existing in different contracts of a project. To prevent function clashing, smart contract developers should ensure that the function names in their contracts are unique and do not result in the same function selector being used multiple times.
Mutable Contract Rug
The CREATE2 opcode, which was introduced in the Constantinople hardfork as part of Ethereum Improvement Proposal (EIP) 1014, allows a contract to be deployed at an address that can be calculated in advance. While this feature can be useful, it can also be exploited to create contracts that can change their own code after being deployed. To prevent this type of vulnerability, Ethereum plans to remove the selfdestruct opcode with EIP-4758, which will remove the ability to create “metamorphic” contracts in the future.
Delegatecall and Selfdestruct
Using delegatecall and selfdestruct together can lead to unexpected behavior in smart contracts. If contract A calls a function in contract B using delegatecall, and the function in contract B includes a selfdestruct command, it is contract A that will be destroyed, rather than contract B. This can be problematic if contract A is intended to be a long-lived contract that performs important functions, and contract B is intended to be a temporary contract that performs auxiliary tasks. To prevent this type of vulnerability, smart contract developers should be aware of the potential consequences of using delegatecall and selfdestruct together, and carefully consider whether this combination is appropriate for their use case.
In summary, there are numerous potential dangers that can arise when using proxy contracts in solidity. It is important for developers to be aware of these vulnerabilities and to take steps to prevent them in order to ensure the security and reliability of their contracts. By carefully considering the potential risks and taking appropriate precautions, smart contract developers can create robust and secure contracts that can be trusted by their users.
Guardian Audits provides results-oriented smart contract audits to ensure the security of mission-critical solidity contracts. Our pay-per-vulnerability model means that our team of experienced auditors have a vested interest in finding as many vulnerabilities as possible, leaving no stone unturned. Guard your smart contracts with Guardian Audits.