transmissions11 / solmate

Modern, opinionated, and gas optimized building blocks for smart contract development.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Disallow Address Poisoning in ERC20

alex-ppg opened this issue · comments

This is a duplicate of an issue I opened in the OpenZeppelin repository with regard to the address poisoning attack we have seen being repetitively exploited in the wild. As they are going to introduce a change for this particular issue, we believe it should be replicated in the solmate dependency as well given its popularity.

📝 Details

The current token contract implementation of the EIP-20 standard does not satisfy all SHOULD clauses of the standard definition and as such permits the commonly-known address poisoning attack by executing transferFrom instructions from arbitrary addresses with an amount of 0.

The problem arises from how transferFrom evaluates the approval between the caller and the sender of the funds. In the EIP-20 standard, the following statement is present:

The function SHOULD throw unless the _from account has deliberately authorized the sender of the message via some mechanism

This condition is not validated for zero-value transfers as no "deliberate" approval is evaluated. To ensure we remain compliant with the EIP-20 standard in full, we cannot disallow zero-value transfers altogether.

As a workaround, we propose that the if clause within transferFrom is refactored to enforce a non-zero approval between the sender of funds and the msg.sender via a require check or if-revert pattern, either of which is acceptable.

This change will ensure maximal compatibility with existing contract systems, conform to the EIP-20 standard to a greater degree, and address the address poisoning attack we have seen being extensively exploited in recent times.

🔢 Code to reproduce bug

A simple Solidity smart contract to illustrate the bug in action:

import "@solmate/tokens/ERC20.sol";

contract Poisoning {
    ERC20 public immutable token;

    constructor() {
        token = new ERC20("Test", "TST", 18);
    }

    function poison(address from_, address to_) external {
        require(token.allowance(from_, address(this)) == 0);
        token.transferFrom(from_, to_, 0);
    }
}

Invoking the poison function with arbitrary from_ and to_ arguments will successfully perform a transferFrom invocation even when the approval between from_ (the sender of funds) and address(this) (the caller of the transferFrom function) has been set to 0.

This behaviour permits polluting the transfer history of an account on blockchain explorers which in turn can cause users to be misled and copy incorrect addresses when performing their own valid transfers.

commented

i consider this to be pretty low severity as its entirely PEBCAK, likely a wontfix from me but will leave open in case others have a strong opinion

While the victimization is a PEBCAK issue (many are surprised it even works including me), block explorers as well as personal wallets being polluted with transactions that overpopulate transaction history is a nuisance. Additionally, the EIP-20 standard itself contains a SHOULD clause which is not being enforced and logically should be. Just some additional points to consider.

Based on on-chain code analysis of existing DeFi projects, this change should never be applied as it can break existing patterns. For more information, consult: OpenZeppelin/openzeppelin-contracts#3931 (comment)