HACK ANALYSIS 4 min read

Understanding Security of Fallback & Recieve Function in Solidity


Understanding Security of Fallback & Recieve Function in Solidity

What is a “fallback” function?

When a function in a contract is called, the fallback function is executed if:

  • None of the other functions match the provided function signature
  • If no data was sent at all and
  • There is no receive Ether function.

The fallback function always receives data, but it must be designated payable to receive ether as well. A contract can only have one fallback function. It can be virtual, can override, and can have modifiers.

What is a “recieve” function?

When the contract is called without any calldata, the receive function is invoked. This function is used for transmitting or receiving plain Ether, such as when using the .send()or .transfer()methods, basic Ether transfer will fall back to it. If neither a receive Ether nor a payable fallback function is available, the contract cannot accept Ether through regular transactions, and it will throw an exception.

Exploitation scenario using fallback/receive function

In the scenario shown below, the fallback mechanism intervenes and calls the vulnerable smart contract multiple times to withdraw the funds after the susceptible smart contract tries to send money to a different address. To further understand this issue, take a look at the following smart contract example:

To exploit the above contract, the attacker will first call the addAllMoneyfunction with their address to get registered in the balances mapping. Once that is done, they’ll create an attacker contract with the fallback function from their attacker contract and call the withdrawAllMoneyfunction with some amount. The line msg.sender.call.value(totalamount)(""); will make a call to the fallback function, which in turn will call the withdrawAllMoney() again, preventing the code flow from decreasing the balance of the msg.sender. The fallback function can be written as shown below. Observe the recursive call to the “withdrawAllMoney” function.

The attacker could also have used a receive()function in place of a fallback since the “fallbackVulnerability” is just sending a msg.valuewithout any msg.data.

What is the difference between Solidity’s fallback() and receive() functions?

From the standpoint of a developer, they may seem similar at first glance (and are practically the same for the end user), yet they are extremely distinct.

fallback() external payable — When no other function matches (not even the receive function).

IBEP20(tokenAddress).transfer(to,Ether)

Let’s consider the above example; Here, we’re calling the contract at tokenAddress externally by use of the transfer function from the IBEP20.

If the external contract includes a function called transfer that accepts an address (to) and an uint256 (in that order) as parameters, the transfer function will be executed.

The fallback() function of the external contract is used in its place if there’s no transfer function, and the transaction is refunded if neither of those functions exists, and there is no fallback function or if you are calling an address that is not a contract.

payable(_to).transfer(_Ether);

In the case of receive(), we’re using the Solidity keyword transfer to send an amount of Ether to a payable address (making an address payable shows that it can send or receive Ether). The receive() function will be triggered if it exists.

Techniques to prevent vulnerabilities in fallback and receive functions:

Fixing and Upgrading a Contract:

Updating the problematic contract to version 0.6.x and declaration of a receive() method that only accepts incoming Ether without data would help to prevent the kind of confusion that might lead to a loss of Ether.

Make fallback methods easy:
When a contract receives a message without any parameters, fallback methods are invoked, and they can only access 2,300 gas when called from a .send() or .transfer() method. The most you can do in a fallback function if you want to be able to receive Ether from a .send() or .transfer() is log an event. If the computation of more gas is required, use a custom-defined function.

Check the length of data in fallback functions:

If the fallback function is solely intended to be used for logging received Ether, you should make sure that the data is empty because it is not just called for simple ether transfers (without data) but also when no other function matches. If your contract is used improperly and functions that do not exist are called, callers won’t be aware of it.

Our product SolidityScan detects incorrect implementation of fallback functions.

Conclusion:

In this article, we talked about the fallback function as a contract-specific function. You can rely on SolidityScan to ensure that the proper steps are taken to achieve top-tier smart contract security. Signup for a free trial at https://solidityscan.com/signup