ethereum / solidity

Solidity, the Smart Contract Programming Language
https://soliditylang.org
GNU General Public License v3.0
23.04k stars 5.7k forks source link

Writing to 0x40 causes a dirty read. #15391

Closed Subway2023 closed 1 week ago

Subway2023 commented 1 week ago

Environment

Steps to Reproduce

contract BugDetectionContract {
    function returnStoredValue() external returns (bytes memory) {
        bytes memory storedValue;
        assembly {
            let memPtr := mload(0x40)
            storedValue := mload(memPtr)
            mstore(add(storedValue, 0x40), 0)

        }
        return storedValue;
    }
}

Legacy Codegen

output

0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020

IR-based Codegen

0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020

Why is there a difference in the return values? Shouldn't they all be 0?

Subway2023 commented 1 week ago
pragma solidity ^0.8.0;
contract C {
    uint[][] a;
    uint[] b;
    function g() public returns (uint[] memory) {
        uint[] storage c = b;
        assembly {
            mstore(0x40, 0)
        }
        return b;
    }
}

have the same problem.

Legacy Codegen

output

000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000

IR-based Codegen

output


000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020
``
ekpyron commented 1 week ago

Solidity uses memory offset 0x40 as free memory pointer for memory management (see https://docs.soliditylang.org/en/v0.8.26/assembly.html#memory-management). Furthermore, it relies on being able to use memory offsets 0-64 as scratch space and memory offset 0x60 as zero pointer. Manually interfering with the free memory pointer results (setting it to values below its initial value) generally results in undefined behaviour. Furthermore, memory cannot be assumed to be zeroed before a first write to it. In the given cases in particular, the value read from memPtr, so the value in storedValue already is undefined. It will probably be zero, which means that the subsequent write to 0x40 sets the free memory pointer to an invalid state. Now preparing returndata results in overlapping reads and write to memory for ABI encoding the returndata.

To produce consistent behaviour you generally need to respect Solidity's memory model within inline assembly, if you mix assembly and high-level code.