Skip to content

fix: copy bech32 cache key to prevent SIGSEGV from mmap'd zero-copy data#3106

Draft
masih wants to merge 1 commit intorelease/v6.4from
cursor/evm-fee-panic-65fb
Draft

fix: copy bech32 cache key to prevent SIGSEGV from mmap'd zero-copy data#3106
masih wants to merge 1 commit intorelease/v6.4from
cursor/evm-fee-panic-65fb

Conversation

@masih
Copy link
Collaborator

@masih masih commented Mar 21, 2026

Problem

Node actic-1 halted at block 149060323 with a SIGSEGV in goroutine 334 (CheckTx):

unexpected fault address 0x7fd4774efb0d
fatal error: fault
[signal SIGSEGV: segmentation violation code=0x1 addr=0x7fd4774efb0d pc=0xdbf5a9]

The crash occurred in memeqbody during a Go map lookup inside the accAddrCache LRU, called from AccAddress.String() in the EVM fee charging path:

CheckTx → EvmCheckTxAnte → EvmCheckAndChargeFees → BuyGas → SubBalance
  → SubUnlockedCoins → NewCoinSpentEvent → AccAddress.String()
  → accAddrCache.Get(key) → mapaccess2_faststr → memeqbody → SIGSEGV

Root Cause

cacheBech32Addr stored the caller-provided cacheKey (created via UnsafeBytesToStr) directly as a map key in the global accAddrCache LRU. UnsafeBytesToStr produces a string that shares backing memory with the original []byte slice.

When that slice originates from a MemIAVL zero-copy read (which is the default — ZeroCopy: true), its backing memory is mmap'd from a snapshot file on disk. During snapshot rotation, Tree.ReplaceWith() closes the old snapshot via snapshot.Close()kvsMap.Close()mmap.Munmap(), unmapping the memory region.

Any LRU cache keys still pointing into that region become dangling pointers. The next map lookup in the LRU triggers memeqbody on unmapped memory → SIGSEGV.

The fault address (0x7fd4774efb0d) and the lookup key data pointer (0x7fd40dc23b0d) are both in the Linux mmap region, not the Go heap (0xc0...), confirming this is unmapped memory-mapped file data.

Fix

Use string(addr) instead of cacheKey when inserting into the LRU cache. string([]byte) always allocates a heap-backed copy, so the cache key survives independently of the original slice's lifetime.

Performance Impact

Zero on the hot path. Cache hits (99%+ of calls) still use the zero-allocation UnsafeBytesToStr for lookup. On cache misses, one extra 20-byte allocation is negligible next to the bech32 encoding already performed on that path.

Scope

The fix is in cacheBech32Addr, which is the single function used by all three address caches (accAddrCache, valAddrCache, consAddrCache), so all three are protected by this one-line change.

Open in Web Open in Cursor 

cacheBech32Addr stored the caller-provided cacheKey (created via
UnsafeBytesToStr) directly in the global LRU cache.  UnsafeBytesToStr
produces a string that shares backing memory with the original []byte
slice.  When that slice originates from a MemIAVL zero-copy read, its
backing memory is mmap'd from a snapshot file on disk.

During snapshot rotation, Tree.ReplaceWith() closes the old snapshot
which calls munmap(), unmapping the memory region.  Any LRU cache keys
still pointing into that region become dangling pointers.  The next map
lookup in the LRU triggers memeqbody on unmapped memory, causing a
SIGSEGV that halts the node.

Fix: use string(addr) instead of cacheKey when inserting into the cache.
string([]byte) always allocates a heap-backed copy, so the cache key
survives independently of the original slice's lifetime.

Performance impact: zero on the hot path (cache hits still use the
zero-alloc UnsafeBytesToStr for lookup). On cache misses, one extra
20-byte allocation is negligible next to the bech32 encoding already
performed.

Co-authored-by: Masih H. Derkani <m@derkani.org>
@github-actions
Copy link

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedMar 21, 2026, 4:43 PM

@codecov
Copy link

codecov bot commented Mar 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 58.39%. Comparing base (0318a62) to head (c9d10ac).

Additional details and impacted files

Impacted file tree graph

@@              Coverage Diff              @@
##           release/v6.4    #3106   +/-   ##
=============================================
  Coverage         58.39%   58.39%           
=============================================
  Files              2088     2088           
  Lines            172135   172135           
=============================================
  Hits             100525   100525           
  Misses            62647    62647           
  Partials           8963     8963           
Flag Coverage Δ
sei-chain-pr 87.61% <100.00%> (?)
sei-db 70.41% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-cosmos/types/address.go 74.91% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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