fix(evm): use deque for execution cache to prevent pointer invalidation#362
fix(evm): use deque for execution cache to prevent pointer invalidation#362starwarfan wants to merge 1 commit intoDTVMStack:mainfrom
Conversation
Runtime functions like evmGetExtCodeHash and evmGetKeccak256 return pointers to bytes32 values stored in ExecutionCache vectors. The JIT defers loading these bytes until the value is consumed by a later opcode. When a second push_back triggers vector reallocation, all previously returned pointers become dangling, causing the JIT to read garbage data and spuriously trip the out-of-gas or overflow traps. Replace std::vector with std::deque for ExtcodeHashes and Keccak256Results, since deque::push_back preserves references to existing elements. Made-with: Cursor
There was a problem hiding this comment.
Pull request overview
Fixes a multipass JIT use-after-free by ensuring pointers returned from runtime helpers (e.g., EXTCODEHASH/KECCAK256) remain valid even after additional cached results are appended during execution.
Changes:
- Switch
ExecutionCache::ExtcodeHashesfromstd::vectortostd::dequeto avoid pointer invalidation on reallocation. - Switch
ExecutionCache::Keccak256Resultsfromstd::vectortostd::dequefor the same reason. - Add the required
<deque>include.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| std::deque<evmc::bytes32> ExtcodeHashes; | ||
| std::deque<evmc::bytes32> Keccak256Results; |
There was a problem hiding this comment.
This fix addresses a subtle JIT use-after-free scenario, but the PR doesn’t add a regression test to ensure pointer stability across multiple runtime calls (e.g., two EXTCODEHASH / KECCAK256 result pointers kept alive and consumed later in multipass JIT mode). Given the repo already has extensive GoogleTest-based EVM tests, please add a targeted test that fails with the previous std::vector implementation and passes with std::deque to prevent regressions.
Runtime functions like
evmGetExtCodeHashandevmGetKeccak256return pointers toevmc::bytes32values stored inExecutionCachevectors. The JIT defers loading these bytes until the value is consumed by a later opcode (e.g. when KECCAK256 reads offset/size produced by prior EXTCODEHASH calls). When a secondpush_backtriggersstd::vectorreallocation, all previously returned pointers become dangling, causing the JIT to read garbage data and spuriously trip the out-of-gas or overflow traps.Fixes 17 reproducing fuzz crashes in multipass JIT mode (status mismatches and gas mismatches involving EXTCODEHASH/KECCAK256).
1. Does this PR affect any open issues?(Y/N) and add issue references (e.g. "fix #123", "re #123".):
2. What is the scope of this PR (e.g. component or file name):
src/runtime/evm_instance.h
3. Provide a description of the PR(e.g. more details, effects, motivations or doc link):
Root cause:
ExecutionCache::ExtcodeHashesandExecutionCache::Keccak256Resultsarestd::vector<evmc::bytes32>. Runtime functions such asevmGetExtCodeHashdopush_backand return a pointer toback().bytes. The multipass JIT stores this pointer in a MIR variable and only dereferences it later (whenconvertBytes32ToU256Operandgenerates loads). If a subsequent runtime call pushes another element, the vector may reallocate, invalidating the earlier pointer. The JIT then loads from freed memory, producing garbage uint256 values whose upper limbs are non-zero, which triggers thenormalizeOperandU64overflow trap (mapped toGasLimitExceeded).Fix: Replace
std::vectorwithstd::dequefor both fields.std::deque::push_backguarantees that references to existing elements remain valid, eliminating the use-after-free.4. Are there any breaking changes?(Y/N) and describe the breaking changes(e.g. more details, motivations or doc link):
5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links:
Verified by re-running all 24 remaining fuzz crash files (post-SLT/SGT fix) with
evmone-fuzzerin multipass mode. 17 previously failing crashes now pass. The remaining 7 crashes are all on Frontier (rev=0) and are unrelated to this fix.6. Release note
Made with Cursor