Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: avoid raising internal python error on large memory access #442

Merged
merged 15 commits into from
Jan 16, 2025

Conversation

0xkarmacoma
Copy link
Collaborator

these can otherwise raise OverflowError: cannot fit 'int' into an index-sized integer

For example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import "forge-std/Test.sol";
import {console2} from "forge-std/console2.sol";

import {SymTest} from "halmos-cheatcodes/SymTest.sol";

contract Test112 is Test, SymTest {
    uint256 MEGA_SIZE = 10**32;

    function setUp() public {
        console2.log("Test112.setUp");
    }

    // good: reverts with Panic(0x41) (too much memory requested)
    function test_megaMem_new_bytes() external {
        bytes memory data = new bytes(MEGA_SIZE);
        console2.logBytes(data);
    }

    // bad: OverflowError: cannot fit 'int' into an index-sized integer
    function test_megaMem_sha3() external {
        bytes32 hash;
        uint256 size = MEGA_SIZE;
        assembly {
            hash := keccak256(0, size)
        }
    }

    function test_megaMem_call_insize() external {
        address addr = address(this);
        uint256 value = 0;
        bool success;
        uint256 in_ptr = 0;
        uint256 in_size = MEGA_SIZE;
        uint256 out_ptr = 0;
        uint256 out_size = 0;

        assembly {
            success := call(gas(), addr, value, in_ptr, in_size, out_ptr, out_size)
        }
    }

    // PASS
    function test_megaMem_call_outsize() external {
        address addr = address(this);
        uint256 value = 0;
        bool success;
        uint256 in_ptr = 0;
        uint256 in_size = 0;
        uint256 out_ptr = 0;
        uint256 out_size = MEGA_SIZE;

        assembly {
            success := call(gas(), addr, value, in_ptr, in_size, out_ptr, out_size)
        }
    }

    function test_megaMem_return() external returns (bytes memory) {
        uint256 size = MEGA_SIZE;
        assembly {
            return(0, size)
        }
    }

    function test_megaMem_log() external {
        uint256 size = MEGA_SIZE;
        assembly {
            log0(0, size)
        }
    }
}

these can otherwise raise `OverflowError: cannot fit 'int' into an index-sized integer`
@0xkarmacoma
Copy link
Collaborator Author

h/t @m4k2

src/halmos/sevm.py Outdated Show resolved Hide resolved
@daejunpark
Copy link
Collaborator

awesome, could you also add these nice examples as tests?

@0xkarmacoma
Copy link
Collaborator Author

awesome, could you also add these nice examples as tests?

do you think they fit as unit tests? The problem is that in both cases we get an error in both cases (but a different flavor), so the returncode is just 1 anyway right?

@0xkarmacoma
Copy link
Collaborator Author

awesome, could you also add these nice examples as tests?

do you think they fit as unit tests? The problem is that in both cases we get an error in both cases (but a different flavor), so the returncode is just 1 anyway right?

actually I think I have a decent pattern to test for this:

    function test_megaMem_sha3(bool coinflip) external {
        bytes32 hash;
        uint256 size = coinflip ? MEGA_SIZE : 32;

        assembly {
            hash := keccak256(0, size)
        }
    }
# before
[ERROR] test_megaMem_sha3(bool)
ERROR    OverflowError: cannot fit 'int' into an index-sized integer  

# after
[PASS] test_megaMem_sha3(bool) (paths: 3, time: 0.04s (paths: 0.04s, models: 0.00s), bounds: [])

@0xkarmacoma
Copy link
Collaborator Author

awesome, could you also add these nice examples as tests?

do you think they fit as unit tests? The problem is that in both cases we get an error in both cases (but a different flavor), so the returncode is just 1 anyway right?

actually I think I have a decent pattern to test for this:

    function test_megaMem_sha3(bool coinflip) external {
        bytes32 hash;
        uint256 size = coinflip ? MEGA_SIZE : 32;

        assembly {
            hash := keccak256(0, size)
        }
    }
# before
[ERROR] test_megaMem_sha3(bool)
ERROR    OverflowError: cannot fit 'int' into an index-sized integer  

# after
[PASS] test_megaMem_sha3(bool) (paths: 3, time: 0.04s (paths: 0.04s, models: 0.00s), bounds: [])

in 0d2aab3

Copy link
Collaborator

@daejunpark daejunpark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great, thanks for thoughtfully handling all the various cases!


if not isinstance(ret_, ByteVec):
raise HalmosException(f"Invalid return value: {ret_}")
data = ret_.slice(0, effective_ret_size)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i understand correctly, this needs the full scan even if effective_ret_size == len(ret_). could you make sure slice() returns immediately in such case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, let me look into it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in 48424ca

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

48424ca looks good. but it addressed the same issue in a different location, leaving this one unaddressed. alternatively, you might consider improving the bytevec.slice(0, size) function to immediately return when size is the full length?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might consider improving the bytevec.slice(0, size) function to immediately return when size is the full length?

that may be a little tricky to handle properly, because bytevecs are mutable. So in principle we need to return a copy, but in this case we can skip the copy because we just blit it in memory and we know we're not going to modify the returndata any further

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll handle it at this callsite and we can think about reducing copies further or introducing immutable bytevecs in a separate PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for the confusion, I refactored both places to call the same function: ebc6da6

src/halmos/sevm.py Outdated Show resolved Hide resolved
offset, offset + size
)
ex.st.memory.set_slice(loc, end_loc, codeslice)
codeslice: ByteVec = account_code.slice(offset, size)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh good catch!

keccak256_op was optimized to return(0, 0), which does not revert, which caused the test to fail

for context, newer foundry versions have the optimizer turned off (but ci uses the stable channel of foundry, which still has the optimizer turned on by default)
@0xkarmacoma 0xkarmacoma enabled auto-merge (squash) January 16, 2025 23:03
@0xkarmacoma 0xkarmacoma merged commit 6163a6c into main Jan 16, 2025
45 checks passed
@0xkarmacoma 0xkarmacoma deleted the fix/python-int-overflow branch January 16, 2025 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants