Skip links

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.

contract NonPayloadAttackableToken {
  modifier onlyPayloadSize(uint size) {
    assert(msg.data.length == size + 4);
    _;
  }

 function transfer(address _to, uint256 _value) onlyPayloadSize(2 * 32) {
   // do stuff
 }   
}

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:

function propose(address _to, uint _value, bytes _data) public returns (bool _success) {
   to = _to;
   value = _value;
   data = _data;

   return true;
}

function confirm() returns (bool _success) {
   if (!to.call.value(value)(data)) {
     throw;
   }

   return true;
}

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.

contract NonPayloadAttackableToken {

  modifier onlyPayloadSize(uint size) {
    assert(msg.data.length >= size + 4);
    _;
  }

 function transfer(address _to, uint256 _value) onlyPayloadSize(2 * 32) {
   // do stuff
 }   
}

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/
    1. call, callcode, and delegatecall https://solidity.readthedocs.io/en/develop/types.html#members-of-addresses
  1. Smart Contract Audit Team

If you liked this article, you might also like: