From 32cd34a845002b0f7f0448707ff7910e728b36e0 Mon Sep 17 00:00:00 2001 From: cl507523 Date: Thu, 26 Feb 2026 07:47:53 +0000 Subject: [PATCH 1/2] fix(evm): pin MLOAD results to prevent stale memory reads in multipass JIT MLOAD's convertBytes32ToU256Operand generates load instructions from the EVM memory buffer that are represented as SSA values in the MIR. When the MLOAD result stays on the EVM stack across a memory-writing opcode (CODECOPY, MSTORE, EXTCODECOPY, etc.), the backend may schedule these loads after the write, causing the MLOAD to observe the *new* memory contents instead of the value at the time of the MLOAD. Pin the four uint64 components into local variables immediately after the loads, creating a data dependency that prevents reordering past subsequent function calls. Fixes 2 fuzz crashes (crash-0e10c1dd, crash-cdb6d9e4) where MLOAD returned code bytes written by a later CODECOPY, producing non-zero upper bits that triggered a spurious GasLimitExceeded. Made-with: Cursor --- src/compiler/evm_frontend/evm_mir_compiler.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index aefdfad7..5c7ec208 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -2693,6 +2693,19 @@ EVMMirBuilder::handleMLoad(Operand AddrComponents) { Operand Bytes32Op(MemPtr, EVMType::BYTES32); Operand Result = convertBytes32ToU256Operand(Bytes32Op); + + // Pin loaded values into local variables so the backend cannot reschedule + // the memory reads past later function calls (e.g. CODECOPY / MSTORE) that + // may modify the same memory region. Without this, an MLOAD result that + // stays on the EVM stack across a memory-writing opcode could observe the + // *new* contents instead of the value at the time of the MLOAD. + U256Inst Parts = extractU256Operand(Result); + for (int I = 0; I < static_cast(EVM_ELEMENTS_COUNT); ++I) { + Variable *Tmp = storeInstructionInTemp(Parts[I], I64Type); + Parts[I] = loadVariable(Tmp); + } + Result = Operand(Parts, EVMType::UINT256); + #ifdef ZEN_ENABLE_EVM_GAS_REGISTER reloadGasFromMemory(); #endif From 4b668d65fd440dbbb83f26a22974854854e9c57f Mon Sep 17 00:00:00 2001 From: ChenLi Date: Sat, 28 Feb 2026 17:49:01 +0800 Subject: [PATCH 2/2] fix: Update src/compiler/evm_frontend/evm_mir_compiler.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/compiler/evm_frontend/evm_mir_compiler.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compiler/evm_frontend/evm_mir_compiler.cpp b/src/compiler/evm_frontend/evm_mir_compiler.cpp index 5c7ec208..5b834371 100644 --- a/src/compiler/evm_frontend/evm_mir_compiler.cpp +++ b/src/compiler/evm_frontend/evm_mir_compiler.cpp @@ -2701,8 +2701,7 @@ EVMMirBuilder::handleMLoad(Operand AddrComponents) { // *new* contents instead of the value at the time of the MLOAD. U256Inst Parts = extractU256Operand(Result); for (int I = 0; I < static_cast(EVM_ELEMENTS_COUNT); ++I) { - Variable *Tmp = storeInstructionInTemp(Parts[I], I64Type); - Parts[I] = loadVariable(Tmp); + Parts[I] = protectUnsafeValue(Parts[I], I64Type); } Result = Operand(Parts, EVMType::UINT256);