Token-0x

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

Scratch Space Collision in Error Handling

Author Revealed upon completion

Root + Impact

Description

  • Normal assembly error handling should preserve existing scratch space data or use dedicated memory regions. The Token-0x implementation unconditionally writes error data to scratch space (0x00-0x3f) without checking for prior usage, causing data corruption in derived contracts that use scratch space for custom validation logic.


  • The _allowance() function uses generic revert(0, 0) while other functions like _approve() write specific error selectors to scratch space .

function _allowance(address owner, address spender) internal view returns (uint256 remaining) {
assembly {
if or(iszero(owner), iszero(spender)) {
@> revert(0, 0) // Generic revert overwrites any scratch space data
}
// ... rest of function
}
}

Risk

Likelihood:

  • Every internal function that validates parameters may overwrite scratch space

  • Derived contracts performing custom validation before calling internal functions will experience data corruption

  • Any function chain that includes validation checks triggers this vulnerability

Impact:

  • Custom error data in derived contracts gets silently overwritten

  • Debugging and error diagnosis becomes impossible due to data loss

  • Potential for incorrect error handling logic based on corrupted scratch space data

Proof of Concept

The test demonstrates that custom data written to scratch space gets overwritten when internal functions revert. A derived contract writing custom error signatures to scratch space will lose that data when the base contract's validation functions execute their own error handling

function test_ScratchSpaceCollision() public {
VulnerableToken baseToken = new VulnerableToken();
address user = makeAddr("user");
// Write custom data to scratch space
assembly {
mstore(0x00, shl(224, 0xdeadbeef))
mstore(0x04, user)
}
// Call function that may overwrite scratch space
try baseToken.balanceOf(address(0)) {
fail("Should revert");
} catch (bytes memory reason) {
// Scratch space was overwritten by internal error handling
assertEq(reason.length, 0, "Generic revert overwrote custom error");
}
}

Recommended Mitigation

Preserve existing scratch space data before writing error information, or use a dedicated memory region for error encoding. The fix should check if scratch space is in use and preserve that data.

function _allowance(address owner, address spender) internal view returns (uint256 remaining) {
assembly {
if or(iszero(owner), iszero(spender)) {
+ // FIX: Use specific error selectors instead of generic revert
+ if iszero(owner) {
+ mstore(0x00, shl(224, 0xe602df05)) // ERC20InvalidApprover
+ mstore(0x04, owner)
+ revert(0x00, 0x24)
+ }
+ if iszero(spender) {
+ mstore(0x00, shl(224, 0x94280d62)) // ERC20InvalidSpender
+ mstore(0x04, spender)
+ revert(0x00, 0x24)
+ }
- revert(0, 0)
}
// ... rest of function
}
}

Support

FAQs

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

Give us feedback!