Smart Contract Short Address Attack Mitigation Failure

Overview

Our  smart contract audit team found that Short Address Attack mitigations can cause several problems with smart contracts.

A Short Address Attack is when a contract receives less data than it was expecting, and Solidity fills the missing bytes with zeros. The deployed smart contract cannot prevent this and will interpret those extra zeros as part of the correct value, provoking serious issues.

One of the first workarounds was suggested by Redditor izqui9 on Reddit. It adds a check to the vulnerable methods to verify if the amount of data is the same amount expected by the contract. Peter Vessens suggests a similar solution.

Issue Found with some Multisig Wallets

The transfer function expects two parameters. Each parameter has 32 bytes and the method signature adds 4 bytes to the call. The expected total size is 68 bytes.

We received a report that this method failed to execute when it was called from Parity multisignature wallet.

We found that the EVM pads call from this multisignature wallet, making the total 96 bytes instead of the expected 68.

This multisignature wallet executes transactions in two steps:

  • An operation is proposed
  • It is confirmed and executed

A simplified multisignature wallet looks like this:

In the proposed method, _data is initially formatted correctly to 68 bytes.

When the final call is made in confirm, it will send 68 bytes to our contract, but Solidity will pad it to 96 bytes.

From the documentation on the call method we have the following:

Furthermore, to interface with contracts that do not adhere to the ABI, the function call is provided which takes an arbitrary number of arguments of any type. These arguments are padded to 32 bytes and concatenated.

This issue also affects any other contracts which check the parameter size in the called method.

A simple fix is to allow calls with more data instead of checking the exact size. The Short Address Attack only appears when the data length is shorter than expected.

Issue Found on Internal Calls to ERC20 Functions

Shorter-than-expected lengths also cause problems. There is a report in OpenZeppelin, one of the most important smart contract libraries, which says that If transfer and transferFrom are used by a subcontract function with fewer arguments, the onlyPayloadSize check will fail. It is not possible to adapt the previous workaround to prevent this issue.

Conclusion

On the one hand, no one has found a way to take advantage of the Short Address Attack if the exchanges verify that the addresses’ format is correct. On the other hand, this mitigation caused problems for some smart contracts. For those reasons, we suggest removing the onlyPayloadSize modifier and enforcing security checks on the exchange.

This experience has taught us that smart contracts are not the best place to check parameter sizes. EVM could add some verifications to prevent these kinds of problems.  

By Ismael Bejarano and Pablo Yabo

Reference:

  1. “The ERC20 Short Address Attack Explained” http://vessenes.com/the-erc20-short-address-attack-explained/
  2. call, callcode, and delegatecall https://solidity.readthedocs.io/en/develop/types.html#members-of-addresses
  3. Smart Contract Audit Team

If you liked this article, you might also like:

  • A Pentester

    You are correct that the very simplistic approach to addressing the short-address attack described above has flaws. However, that does not mean that contracts cannot safely mitigate the attack.

    Your first objection, that some clients pad message data, can be easily addressed by checking if msg.data.length is *at least* the expected length of the call data. Granted, there may exist clients that are vulnerable to the short address attack *and* pad message data and would therefore allow the attack to evade detection, but this approach would still detect other cases and not cause any problems besides a trivial increase in gas cost.

    The second objection, that internal calls don’t rewrite msg.data, can be easily addressed in two ways:
    * Before checking msg.data.length, first check if the first four bytes of msg.data match the function hash. If they don’t, then the call is internal and no check of msg.data.length should be performed.
    * Don’t allow public functions in your contracts that are subject to the short address attack. Instead, use only external functions that check msg.data.length and then call private/internal functions that do the work.

    The short-address attack is really a problem with certain clients (that they don’t serialize message data properly) and with the EVM (that it zero-fills overreads from msg.data rather than throwing), but contract owners don’t have any control over those and they’re hard to fix. However, contract owners can easily mitigate the issue in their own contracts and therefore protect their users; until such time as the problem is fixed upstream, best practice is to mitigate.

  • Only check for payloads to be greater than the padded addresses (which should be 20 bytes). What is the problem with this solution?

    Also I think this should be fixed by the EVM or by clients, not by smart contracts.