From f12a2db2fdb62e5e14808e45cfcf4b9a0eccde0b Mon Sep 17 00:00:00 2001 From: cl507523 Date: Thu, 22 Jan 2026 06:10:58 +0000 Subject: [PATCH 1/3] fix(evm): fix memory check in interpreter --- src/evm/opcode_handlers.cpp | 140 ++++++++++++++++++++---------------- 1 file changed, 77 insertions(+), 63 deletions(-) diff --git a/src/evm/opcode_handlers.cpp b/src/evm/opcode_handlers.cpp index 058ef6403..8b3d0961e 100644 --- a/src/evm/opcode_handlers.cpp +++ b/src/evm/opcode_handlers.cpp @@ -264,7 +264,10 @@ bool checkMemoryExpandAndChargeGas(EVMFrame *Frame, const intx::uint256 &Offset, // Convert uint256 to uint64 uint64_t uint256ToUint64(const intx::uint256 &Value) { - return static_cast(Value & 0xFFFFFFFFFFFFFFFFULL); + if ((Value[3] | Value[2] | Value[1]) != 0) { + return std::numeric_limits::max(); + } + return Value[0]; } } // anonymous namespace @@ -805,9 +808,14 @@ void Keccak256Handler::doExecute() { const auto Offset = Frame->pop(); const auto Length = Frame->pop(); - const size_t MemOffset = static_cast(Offset); - const size_t DataLength = static_cast(Length); + if (!checkMemoryExpandAndChargeGas(Frame, Offset, Length)) { + getContext()->setStatus(EVMC_OUT_OF_GAS); + return; + } + const uint64_t DataLength = static_cast(Length); + const uint64_t MemOffset = + DataLength == 0 ? 0 : static_cast(Offset); const uint64_t ExtraGas = static_cast(numWords(static_cast(DataLength))) * 6; // Calculate the gas cost of keccak itself based on word count @@ -816,11 +824,6 @@ void Keccak256Handler::doExecute() { return; } - if (!checkMemoryExpandAndChargeGas(Frame, MemOffset, DataLength)) { - getContext()->setStatus(EVMC_OUT_OF_GAS); - return; - } - const uint8_t *InputData = Frame->Memory.data() + MemOffset; uint8_t HashResult[32]; @@ -839,11 +842,11 @@ void MStoreHandler::doExecute() { intx::uint256 OffsetVal = Frame->pop(); intx::uint256 Value = Frame->pop(); - uint64_t Offset = uint256ToUint64(OffsetVal); - if (!checkMemoryExpandAndChargeGas(Frame, Offset, 32)) { + if (!checkMemoryExpandAndChargeGas(Frame, OffsetVal, 32)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } + uint64_t Offset = static_cast(OffsetVal); uint8_t ValueBytes[32]; intx::be::store(ValueBytes, Value); @@ -859,12 +862,11 @@ void MStore8Handler::doExecute() { intx::uint256 OffsetVal = Frame->pop(); intx::uint256 Value = Frame->pop(); - uint64_t Offset = uint256ToUint64(OffsetVal); - if (!checkMemoryExpandAndChargeGas(Frame, Offset, 1)) { + if (!checkMemoryExpandAndChargeGas(Frame, OffsetVal, 1)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } - + uint64_t Offset = static_cast(OffsetVal); uint8_t ByteValue = static_cast(Value & intx::uint256{0xFF}); Frame->Memory[Offset] = ByteValue; } @@ -876,11 +878,11 @@ void MLoadHandler::doExecute() { EVM_STACK_CHECK(Frame, 1); intx::uint256 OffsetVal = Frame->pop(); - uint64_t Offset = uint256ToUint64(OffsetVal); - if (!checkMemoryExpandAndChargeGas(Frame, Offset, 32)) { + if (!checkMemoryExpandAndChargeGas(Frame, OffsetVal, 32)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } + uint64_t Offset = static_cast(OffsetVal); uint8_t ValueBytes[32]; // TODO: use EVMMemory class in the future @@ -982,8 +984,8 @@ void MCopyHandler::doExecute() { return; } - uint64_t DestOffset = uint256ToUint64(DestOffsetVal); - uint64_t Offset = uint256ToUint64(OffsetVal); + uint64_t DestOffset = static_cast(DestOffsetVal); + uint64_t Offset = static_cast(OffsetVal); uint64_t Size = uint256ToUint64(SizeVal); if (copyCodeAndChargeGas(Frame, Size) == false) { @@ -1029,13 +1031,16 @@ void ReturnHandler::doExecute() { intx::uint256 OffsetVal = Frame->pop(); intx::uint256 SizeVal = Frame->pop(); - uint64_t Offset = uint256ToUint64(OffsetVal); - uint64_t Size = uint256ToUint64(SizeVal); - if (!checkMemoryExpandAndChargeGas(Frame, Offset, Size)) { + // First check memory expansion with uint256 values + if (!checkMemoryExpandAndChargeGas(Frame, OffsetVal, SizeVal)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } + // Only convert to uint64 after successful memory check + uint64_t Size = static_cast(SizeVal); + uint64_t Offset = Size == 0 ? 0 : static_cast(OffsetVal); + // TODO: use EVMMemory class in the future std::vector ReturnData(Frame->Memory.begin() + Offset, Frame->Memory.begin() + Offset + Size); @@ -1059,13 +1064,16 @@ void RevertHandler::doExecute() { intx::uint256 OffsetVal = Frame->pop(); intx::uint256 SizeVal = Frame->pop(); - uint64_t Offset = uint256ToUint64(OffsetVal); - uint64_t Size = uint256ToUint64(SizeVal); - if (!checkMemoryExpandAndChargeGas(Frame, Offset, Size)) { + // First check memory expansion with uint256 values + if (!checkMemoryExpandAndChargeGas(Frame, OffsetVal, SizeVal)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } + // Only convert to uint64 after successful memory check + uint64_t Size = static_cast(SizeVal); + uint64_t Offset = Size == 0 ? 0 : static_cast(OffsetVal); + std::vector RevertData(Frame->Memory.begin() + Offset, Frame->Memory.begin() + Offset + Size); @@ -1203,27 +1211,29 @@ void CreateHandler::doExecute() { return; } - if (!expandMemoryAndChargeGas(Frame, - uint256ToUint64(CodeOffset + CodeSizeVal))) { + // First check memory expansion with uint256 values + if (!checkMemoryExpandAndChargeGas(Frame, CodeOffset, CodeSizeVal)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } - - evmc_message NewMsg{ - .kind = (OpCode == OP_CREATE2 ? evmc_call_kind::EVMC_CREATE2 - : evmc_call_kind::EVMC_CREATE), - .flags = 0u, - .depth = Frame->Msg.depth + 1, - .gas = Frame->Msg.gas, - .recipient = {}, - .sender = Frame->Msg.recipient, - .input_data = Frame->Memory.data() + uint256ToUint64(CodeOffset), - .input_size = uint256ToUint64(CodeSizeVal), - .value = intx::be::store(Value), - .create2_salt = intx::be::store(Salt), - .code_address = {}, - .code = nullptr, - .code_size = 0}; + uint64_t CodeSize = static_cast(CodeSizeVal); + uint64_t CodeOffset64 = CodeSize == 0 ? 0 : static_cast(CodeOffset); + + evmc_message NewMsg{.kind = + (OpCode == OP_CREATE2 ? evmc_call_kind::EVMC_CREATE2 + : evmc_call_kind::EVMC_CREATE), + .flags = 0u, + .depth = Frame->Msg.depth + 1, + .gas = Frame->Msg.gas, + .recipient = {}, + .sender = Frame->Msg.recipient, + .input_data = Frame->Memory.data() + CodeOffset64, + .input_size = CodeSize, + .value = intx::be::store(Value), + .create2_salt = intx::be::store(Salt), + .code_address = {}, + .code = nullptr, + .code_size = 0}; // EIP-150 if (Rev >= EVMC_TANGERINE_WHISTLE) { @@ -1360,21 +1370,24 @@ void CallHandler::doExecute() { return; } - if (InputSize != 0) { - if (!expandMemoryAndChargeGas(Frame, - uint256ToUint64(InputOffset + InputSize))) { - Context->setStatus(EVMC_OUT_OF_GAS); - return; - } + // Check memory expansion with uint256 values first + if (!checkMemoryExpandAndChargeGas(Frame, InputOffset, InputSize)) { + Context->setStatus(EVMC_OUT_OF_GAS); + return; } - if (OutputSize != 0) { - if (!expandMemoryAndChargeGas(Frame, - uint256ToUint64(OutputOffset + OutputSize))) { - Context->setStatus(EVMC_OUT_OF_GAS); - return; - } + if (!checkMemoryExpandAndChargeGas(Frame, OutputOffset, OutputSize)) { + Context->setStatus(EVMC_OUT_OF_GAS); + return; } + // Only convert to uint64 after successful memory checks + uint64_t InputSize64 = static_cast(InputSize); + uint64_t InputOffset64 = + InputSize64 == 0 ? 0 : static_cast(InputOffset); + uint64_t OutputSize64 = static_cast(OutputSize); + uint64_t OutputOffset64 = + OutputSize64 == 0 ? 0 : static_cast(OutputOffset); + evmc_message NewMsg{ .kind = CallKind, .flags = (OpCode == evmc_opcode::OP_STATICCALL) ? uint32_t{EVMC_STATIC} @@ -1386,8 +1399,8 @@ void CallHandler::doExecute() { : Frame->Msg.recipient, .sender = (OpCode == OP_DELEGATECALL) ? Frame->Msg.sender : Frame->Msg.recipient, - .input_data = Frame->Memory.data() + uint256ToUint64(InputOffset), - .input_size = uint256ToUint64(InputSize), + .input_data = Frame->Memory.data() + InputOffset64, + .input_size = InputSize64, .value = (OpCode == OP_DELEGATECALL) ? Frame->Msg.value : intx::be::store(Value), @@ -1422,10 +1435,10 @@ void CallHandler::doExecute() { Result.output_data, Result.output_data + Result.output_size)); const auto CopySize = - std::min((size_t)uint256ToUint64(OutputSize), Result.output_size); + std::min(static_cast(OutputSize64), Result.output_size); if (CopySize > 0) { - std::memcpy(Frame->Memory.data() + uint256ToUint64(OutputOffset), - Result.output_data, CopySize); + std::memcpy(Frame->Memory.data() + OutputOffset64, Result.output_data, + CopySize); } const uint64_t CallGas = @@ -1464,15 +1477,16 @@ void LogHandler::doExecute() { intx::uint256 OffsetVal = Frame->pop(); intx::uint256 SizeVal = Frame->pop(); - uint64_t Offset = uint256ToUint64(OffsetVal); - uint64_t Size = uint256ToUint64(SizeVal); - uint64_t ReqSize = Offset + Size; - - if (!expandMemoryAndChargeGas(Frame, ReqSize)) { + // First check memory expansion with uint256 values + if (!checkMemoryExpandAndChargeGas(Frame, OffsetVal, SizeVal)) { Context->setStatus(EVMC_OUT_OF_GAS); return; } + // Only convert to uint64 after successful memory check + uint64_t Size = static_cast(SizeVal); + uint64_t Offset = Size == 0 ? 0 : static_cast(OffsetVal); + // Charge additional gas for log data (8 gas per byte) uint64_t LogDataCost = 8 * Size; if (!chargeGas(Frame, LogDataCost)) { From dfbd41e803ef9a2342bba13ec050b2dcb361f651 Mon Sep 17 00:00:00 2001 From: cl507523 Date: Thu, 22 Jan 2026 06:59:25 +0000 Subject: [PATCH 2/3] fix: returndatacopy --- src/evm/opcode_handlers.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/evm/opcode_handlers.cpp b/src/evm/opcode_handlers.cpp index 8b3d0961e..9d6283cbc 100644 --- a/src/evm/opcode_handlers.cpp +++ b/src/evm/opcode_handlers.cpp @@ -611,6 +611,15 @@ void ReturnDataCopyHandler::doExecute() { intx::uint256 DestOffsetVal = Frame->pop(); intx::uint256 OffsetVal = Frame->pop(); intx::uint256 SizeVal = Frame->pop(); + + const auto &ReturnData = Context->getReturnData(); + // EIP-211: RETURNDATACOPY reverts if offset + size > returndata.size() + if (OffsetVal > ReturnData.size() || SizeVal > ReturnData.size() || + OffsetVal + SizeVal > ReturnData.size()) { + Context->setStatus(EVMC_INVALID_MEMORY_ACCESS); + return; + } + // Ensure memory is large enough if (!checkMemoryExpandAndChargeGas(Frame, DestOffsetVal, SizeVal)) { Context->setStatus(EVMC_OUT_OF_GAS); @@ -625,14 +634,6 @@ void ReturnDataCopyHandler::doExecute() { return; } - const auto &ReturnData = Context->getReturnData(); - - // EIP-211: RETURNDATACOPY reverts if offset + size > returndata.size() - if (Offset > ReturnData.size() || Size > ReturnData.size() - Offset) { - Context->setStatus(EVMC_INVALID_MEMORY_ACCESS); - return; - } - // Copy return data to memory if (Size > 0) { std::memcpy(Frame->Memory.data() + DestOffset, ReturnData.data() + Offset, From 161359822add8f5e3a66f36d1bb9bdd993da7b73 Mon Sep 17 00:00:00 2001 From: cl507523 Date: Thu, 22 Jan 2026 07:07:00 +0000 Subject: [PATCH 3/3] fix: add passed tests --- .../EVMOneInterpreterUnitTestsRunList.txt | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/tests/evmone_unittests/EVMOneInterpreterUnitTestsRunList.txt b/tests/evmone_unittests/EVMOneInterpreterUnitTestsRunList.txt index 0e482ff4e..44f1819a4 100644 --- a/tests/evmone_unittests/EVMOneInterpreterUnitTestsRunList.txt +++ b/tests/evmone_unittests/EVMOneInterpreterUnitTestsRunList.txt @@ -96,6 +96,7 @@ multi_vm/evm.create_initcode_gas_cost/external_vm multi_vm/evm.create2_initcode_gas_cost/external_vm multi_vm/evm.blobbasefee_1/external_vm multi_vm/evm.blobbasefee_dede/external_vm +multi_vm/evm.memory_access/external_vm multi_vm/evm.memory_and_not/external_vm multi_vm/evm.msize/external_vm multi_vm/evm.calldatacopy/external_vm @@ -156,4 +157,46 @@ multi_vm/evm.extcall_undefined/external_vm multi_vm/evm.extdelegatecall_undefined/external_vm multi_vm/evm.extstaticcall_undefined/external_vm multi_vm/evm.eofcreate_undefined/external_vm -multi_vm/evm.returncode_undefined/external_vm \ No newline at end of file +multi_vm/evm.returncode_undefined/external_vm +multi_vm/evm.account_info_homestead/external_vm +multi_vm/evm.arith/external_vm +multi_vm/evm.balance/external_vm +multi_vm/evm.blobhash_empty/external_vm +multi_vm/evm.blobhash_invalid_hash_version/external_vm +multi_vm/evm.blobhash_one/external_vm +multi_vm/evm.blobhash_two/external_vm +multi_vm/evm.call_oog_after_balance_check/external_vm +multi_vm/evm.call_recipient_and_code_address/external_vm +multi_vm/evm.call_then_oog/external_vm +multi_vm/evm.call_value/external_vm +multi_vm/evm.call_value_zero_to_nonexistent_account/external_vm +multi_vm/evm.call_with_value/external_vm +multi_vm/evm.call_with_value_low_gas/external_vm +multi_vm/evm.callcode_new_account_create/external_vm +multi_vm/evm.callcode_then_oog/external_vm +multi_vm/evm.create_failure/external_vm +multi_vm/evm.delegatecall/external_vm +multi_vm/evm.delegatecall_static/external_vm +multi_vm/evm.delegatecall_then_oog/external_vm +multi_vm/evm.eip2929_selfdestruct_cold_beneficiary/external_vm +multi_vm/evm.eip2929_selfdestruct_warm_beneficiary/external_vm +multi_vm/evm.evmone_block_gas_cost_overflow_create/external_vm +multi_vm/evm.evmone_block_max_stack_growth_overflow/external_vm +multi_vm/evm.evmone_block_stack_req_overflow/external_vm +multi_vm/evm.extcodecopy_memory_cost/external_vm +multi_vm/evm.inner_selfdestruct/external_vm +multi_vm/evm.log/external_vm +multi_vm/evm.log_data_cost/external_vm +multi_vm/evm.mcopy/external_vm +multi_vm/evm.return_empty_buffer_at_high_offset/external_vm +multi_vm/evm.returndatacopy/external_vm +multi_vm/evm.returndatacopy_cost/external_vm +multi_vm/evm.returndatacopy_empty/external_vm +multi_vm/evm.returndatacopy_outofrange/external_vm +multi_vm/evm.returndatacopy_outofrange_highbits/external_vm +multi_vm/evm.returndatasize/external_vm +multi_vm/evm.selfdestruct_no_gas_refund/external_vm +multi_vm/evm.sload_cost_pre_tangerine_whistle/external_vm +multi_vm/evm.staticcall_then_oog/external_vm +multi_vm/evm.staticmode/external_vm +multi_vm/evm.storage/external_vm