Skip to content

fix(evm): use deque for execution cache to prevent pointer invalidation#362

Open
starwarfan wants to merge 1 commit intoDTVMStack:mainfrom
starwarfan:fix-cache-use-after-free
Open

fix(evm): use deque for execution cache to prevent pointer invalidation#362
starwarfan wants to merge 1 commit intoDTVMStack:mainfrom
starwarfan:fix-cache-use-after-free

Conversation

@starwarfan
Copy link
Contributor

Runtime functions like evmGetExtCodeHash and evmGetKeccak256 return pointers to evmc::bytes32 values stored in ExecutionCache vectors. 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 second push_back triggers std::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.

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".):

  • N
  • Y

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):

  • Affects user behaviors
  • Contains CI/CD configuration changes
  • Contains documentation changes
  • Contains experimental features
  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Other

Root cause: ExecutionCache::ExtcodeHashes and ExecutionCache::Keccak256Results are std::vector<evmc::bytes32>. Runtime functions such as evmGetExtCodeHash do push_back and return a pointer to back().bytes. The multipass JIT stores this pointer in a MIR variable and only dereferences it later (when convertBytes32ToU256Operand generates 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 the normalizeOperandU64 overflow trap (mapped to GasLimitExceeded).

Fix: Replace std::vector with std::deque for both fields. std::deque::push_back guarantees 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):

  • N
  • Y

5. Are there test cases for these changes?(Y/N) select and add more details, references or doc links:

  • Unit test
  • Integration test
  • Benchmark (add benchmark stats below)
  • Manual test (add detailed scripts or steps below)
  • Other

Verified by re-running all 24 remaining fuzz crash files (post-SLT/SGT fix) with evmone-fuzzer in 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

fix(evm): use std::deque for execution cache hash results to prevent use-after-free when JIT defers pointer dereference across multiple runtime calls.

Made with Cursor

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
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

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::ExtcodeHashes from std::vector to std::deque to avoid pointer invalidation on reallocation.
  • Switch ExecutionCache::Keccak256Results from std::vector to std::deque for the same reason.
  • Add the required <deque> include.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +145 to +146
std::deque<evmc::bytes32> ExtcodeHashes;
std::deque<evmc::bytes32> Keccak256Results;
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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