Unsafe External Calls and Their Vulnerabilities
In smart contract development, especially in decentralized finance (DeFi) and cross-chain protocols, making external calls is often necessary. However, unsafe external calls—especially those made to user-controlled addresses or with unvalidated calldata—can open the door to some of the largest exploits ever seen in the blockchain space. One of the most notable examples is the Poly Network hack of August 2021, where $611 million was drained due to a vulnerability involving an unsafe external call.
In this section, we will explore the mechanics behind unsafe external calls, the risks they pose, and how to implement secure practices to prevent similar vulnerabilities in your smart contracts.
Understanding Unsafe External Calls
An external call occurs when a smart contract interacts with another contract or an external address. External calls are fundamental to blockchain protocols, but they come with risks, especially when a contract makes an external call without verifying the recipient address or ensuring the calldata is safe.
The danger lies in the fact that once a contract makes an external call, it loses control over execution flow, especially if the recipient is a malicious contract. Attackers can exploit this lack of control to re-enter the calling contract, modify state variables, or execute unintended logic.
How the Poly Network Hack Happened
The Poly Network hack is a prime example of how unsafe external calls can lead to devastating results. Poly Network, a cross-chain protocol that connected 35 different blockchains, was hacked for $611 million through a vulnerability in its EthCrossChainManager contract.
Vulnerability Breakdown:
The EthCrossChainManager was a privileged contract responsible for verifying cross-chain transactions.
It contained a function called
verifyHeaderAndExecuteTx
that validated transactions and then called another internal function: _executeCrossChainTx._executeCrossChainTx made an external call to a variable _toContract, which was user-controlled—any user could pass in the contract address they wanted.
No validation checks were implemented to ensure that the external call was being made to a legitimate or trusted contract.
Additionally, _toContract accepted a custom _method variable (user-controlled calldata) that allowed users to execute arbitrary logic on the external contract.
The vulnerability came from the fact that any user could manipulate the _toContract and _method variables to force the privileged EthCrossChainManager to make external calls to any contract with any calldata.
Attack Flow:
Arbitrary Contract Call: The attacker passed in a malicious contract address to _toContract, and in the _method variable, they brute-forced the correct calldata that would allow them to bypass security checks and execute privileged functions in the target contract.
Privileged Contract Exploitation: The attacker tricked the EthCrossChainManager into calling another Poly Network contract—EthCrossChainData. By passing in malicious calldata, they successfully called the function putCurEpochConPubKeyBytes, which allowed them to drain funds.
Complete Fund Drain: Once the attacker gained control of this function, they were able to manipulate the contract and drain over $600 million worth of assets.
Why External Calls Are Dangerous
Loss of Control: When a contract makes an external call, control of execution flow is handed over to the external contract. If the recipient contract is malicious or poorly designed, it can manipulate the calling contract’s state.
Reentrancy Attacks: Unchecked external calls can lead to reentrancy attacks, where the recipient contract re-enters the calling contract and executes code multiple times before the original call is finished.
Custom Calldata Exploits: As in the Poly Network example, allowing user-controlled calldata without validation can lead to attackers executing arbitrary functions, bypassing security checks.
Best Practices to Mitigate Unsafe External Call Vulnerabilities
To avoid falling victim to vulnerabilities like those seen in the Poly Network hack, it is essential to follow best practices when making external calls in smart contracts.
1. Validate External Contract Addresses
Always ensure that external calls are only made to trusted and validated contract addresses. Avoid accepting user-controlled contract addresses unless absolutely necessary.
Use SafeERC20 for Token Transfers
If transferring tokens during an external call, use safe transfer methods such as OpenZeppelin’s SafeERC20 to ensure that the target contract is properly verified and the transfer executes correctly.
Limit User-Provided Data
Be cautious when accepting user-controlled calldata or function selectors. If you must allow custom data, validate that the calldata aligns with the contract’s intended functionality.
Use Reentrancy Guards
Protect your contract’s functions with a reentrancy guard to ensure that external calls cannot re-enter your contract and modify state unexpectedly.
Check Return Values
Always check the return values of external calls to ensure that the call was successful and executed as expected. If the call fails or returns false, revert the transaction.
Last updated