Low-Level Calls Returning True for Non-Existent Contracts

Vulnerability Overview: Low-Level Calls

Low-level functions such as call, delegatecall, and staticcall in Solidity are commonly used to interact with external contracts. These calls do not revert on failure by default, and instead return a boolean value indicating whether the call was successful. However, this can be misleading since these calls return true even if the target address does not exist or contains no code.

When a contract performs a low-level call to an address that is not a valid contract, it may still proceed as if the operation was successful. This can result in major logic failures, including allowing funds to be mismanaged, unauthorized transfers, or bypassing security mechanisms.

How the Exploit Happens

Here’s a simplified scenario of how this exploit can be triggered:

  1. Low-Level Call to Non-Existent Contract: A contract uses a low-level call to interact with another contract or address.

  2. No Validation of Target Address: The calling contract does not verify if the target address is a valid contract.

  3. False Success: Even if the target address is non-existent, the low-level call returns true, leading the calling contract to believe the operation succeeded.

  4. Potential Exploitation: An attacker could exploit this by providing non-existent or malicious addresses as targets for low-level calls, causing unintended behavior, including fund loss or bypassing important checks.

Mitigation: Validating Contract Existence Before Low-Level Calls

To avoid this vulnerability, it is essential to verify that the target address is a valid contract before making a low-level call. One of the simplest ways to do this is by using Solidity’s extcodesize or the OpenZeppelin Address.isContract() function, which checks if an address is a contract by ensuring that it contains bytecode.

Conclusion

Low-level calls in Solidity are powerful but come with inherent risks, particularly when interacting with non-existent addresses. By default, these calls return true even if the target address is invalid, leading to potential vulnerabilities and unintended behavior in smart contracts.

To mitigate this issue:

  • Always check if the target address is a valid contract using extcodesize or OpenZeppelin’s Address.isContract() function.

  • Revert on failure: Ensure that low-level calls are properly handled, and revert the transaction if the call fails.

  • Write comprehensive tests to ensure your smart contracts handle external interactions securely and as expected.

By following these best practices, you can significantly reduce the risk of exploits caused by low-level calls returning true for non-existent contracts, ensuring that your smart contracts are more secure and robust.

Key Takeaways:

  • Low-level calls (call, delegatecall, staticcall) return true by default, even for non-existent contracts.

  • Always validate that the target address is a contract before making a low-level call.

  • Use OpenZeppelin’s Address.isContract() to safely verify the existence of a contract.

  • Handle errors properly and revert on failure to prevent false success indicators.

Last updated