Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: medium
Likelihood: medium

ERC20 log3 Memory Safety Issue

Author Revealed upon completion

ERC20 log3 Memory Safety Issue + Medium

Description

  • Normally, the _approve and _transfer functions emit ERC20 events (Approval and Transfer) using log3 to signal blockchain clients about changes in allowances and balances.

In this codebase, the event emission uses a hardcoded memory pointer 0x00, which is not safe, because Solidity reserves 0x00 for internal memory operations. Writing to this address can overwrite memory used elsewhere in the contract or in inherited contracts, potentially corrupting memory or event data.

// Root cause in the codebase
// In _approve function
mstore(0x00, value)
log3(0x00, 0x20, 0x8c5be1e5..., owner, spender) @> unsafe memory usage
// In _transfer function
mstore(ptr, value)
log3(ptr, 0x20, 0xddf252ad..., from, to) @> ptr is allocated dynamically, but earlier reverts still use 0x00

Risk

Likelihood: Medium

  • Overwriting 0x00 occurs whenever _approve or _transfer is called, as 0x00 is used directly to store event data.

  • In contracts that inherit ERC20Internals or perform other assembly operations, this memory collision can propagate and produce unpredictable behavior.

Impact: Medium

  • Memory corruption can cause incorrect event logs, making blockchain clients or indexers record wrong Transfer or Approval data.

  • Other assembly operations could read corrupted memory, leading to token accounting errors or unexpected reverts.

Proof of Concept

This PoC shows that using 0x00 in _approve or _transfer can overwrite memory, leading to unpredictable behavior.

contract TestLog3Overwrite is ERC20Internals {
function testApprove() external {
_approve(msg.sender, address(this), 100);
assembly {
let memValue := mload(0x00) // could be overwritten by _approve
if eq(memValue, 100) { revert(0, 0) }
}
}
function testTransfer(address to) external {
_transfer(msg.sender, to, 50);
assembly {
let memValue := mload(0x00) // could be affected by _transfer revert path
if eq(memValue, 50) { revert(0, 0) }
}
}
}

Recommended Mitigation

Replace all 0x00 memory usage in log3 or revert sequences with dynamically allocated memory from the free memory pointer mload(0x40).

_approve function

- mstore(0x00, value)
- log3(0x00, 0x20, 0x8c5be1e5..., owner, spender)
+ let logPtr := mload(0x40)
+ mstore(logPtr, value)
+ log3(logPtr, 0x20, 0x8c5be1e5..., owner, spender)
+ mstore(0x40, add(logPtr, 0x20))

_transfer function

- mstore(0x00, shl(224, 0xe450d38c))
- mstore(add(0x00, 4), from)
- mstore(add(0x00, 0x24), fromAmount)
- mstore(add(0x00, 0x44), value)
- revert(0x00, 0x64)
+ let revertPtr := mload(0x40)
+ mstore(revertPtr, shl(224, 0xe450d38c))
+ mstore(add(revertPtr, 4), from)
+ mstore(add(revertPtr, 0x24), fromAmount)
+ mstore(add(revertPtr, 0x44), value)
+ revert(revertPtr, 0x64)

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!