Token-0x

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

L012 Improper Free Memory Pointer Management

Author Revealed upon completion

Root + Impact

Improper Free Memory Pointer Management in Inline Assembly Causes Memory Corruption

Description

  • Under Solidity’s memory model, the free memory pointer stored at slot 0x40 must always point to the next unused memory location. When inline assembly temporarily uses memory starting at the free memory pointer, it is expected to advance or restore this pointer so that subsequent allocations do not overlap.

  • In the codebase, multiple internal functions load the free memory pointer with mload(0x40) and write temporary data at that location but never update the pointer afterward. This leaves the free memory pointer pointing to already-used memory, allowing later Solidity or assembly allocations to overwrite or be overwritten, leading to silent memory corruption and undefined behavior.

// Root cause in the codebase
function _transfer(address from, address to, uint256 value) internal returns (bool success) {
assembly ("memory-safe") {
...
// @> Free memory pointer loaded
let ptr := mload(0x40)
// @> Temporary data written at ptr and ptr + 0x20
mstore(ptr, from)
mstore(add(ptr, 0x20), baseSlot)
...
mstore(ptr, value)
log3(ptr, 0x20, TRANSFER, from, to)
// @> Missing: mstore(0x40, add(ptr, 0x40))
}
}

The same pattern appears in _approve, _allowance, _mint, _burn, _spendAllowance, and _balanceOf.

Risk

Likelihood:

  • Occurs during execution paths that call multiple inline-assembly-based internal functions which rely on the free memory pointer and are followed by Solidity-level memory allocations.

  • Occurs when compiler-generated code or another assembly block allocates memory after one of these functions has written to ptr without advancing 0x40.

Impact:

  • Silent memory corruption that can alter return values, event payloads, or ABI-encoded data.

  • Undefined and non-deterministic behavior that may surface only under specific call sequences, making issues hard to detect and debug.

Proof of Concept

< this test does not work but it shows the kind of bug which can appears >

The following test demonstrates how failing to advance the free memory pointer can corrupt subsequent Solidity memory allocations. A Solidity function allocates memory for strings before and after an inline-assembly call that mismanages 0x40. The second allocation may reuse already-written memory and return corrupted data.

Explanation
The _mint function uses ptr := mload(0x40) for temporary storage but does not advance the pointer. When name() or symbol() is called afterward, Solidity allocates memory starting from the same pointer, potentially overwriting data and returning incorrect results.

function test_memoryCorruption_dueToFreeMemoryPointer() public {
// First Solidity memory allocation
string memory nameBefore = token.name();
// Assembly function that writes at mload(0x40) but does not advance the pointer
token.mint(address(0xBEEF), 1);
// Second Solidity memory allocation
string memory nameAfter = token.name();
// These values should be identical, but may differ due to memory corruption
assertEq(
keccak256(bytes(nameBefore)),
keccak256(bytes(nameAfter))
);
}

This test can fail unpredictably depending on compiler output and execution order, which is characteristic of free-memory-pointer misuse.

Recommended Mitigation

Advance the free memory pointer in every function that uses ptr := mload(0x40) for temporary memory, ensuring the pointer always references unused memory.

Example fix for _transfer(address from, address to, uint256 value):

function _transfer(address from, address to, uint256 value) internal returns (bool success) {
assembly ("memory-safe") {
...
let ptr := mload(0x40)
// existing logic using ptr
mstore(ptr, value)
log3(ptr, 0x20, TRANSFER, from, to)
- // missing free memory pointer update
+ // advance free memory pointer to avoid memory reuse
+ mstore(0x40, add(ptr, 0x40))
}
}

The same adjustment should be applied consistently in _approve, _allowance, _mint, _burn, _spendAllowance, and _balanceOf wherever temporary memory is allocated using the free memory pointer.

Support

FAQs

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

Give us feedback!