Reproduced Exploit

NFTMirror — lzReceive for releaseOnEid results in OOG

1. releaseOnEid builds LZ options via getSendOptions(tokenIds). 2. Budget = 80_000 + 20_000 * length — too low for destination mint/transfer (and worse with transfer validators). 3. DVN lzReceive with that stipend OOGs; payload stored for retry. 4. Every release path needs manual higher-gas retry.

Dec 2024Otheruntagged2 min read

Chain

Other

Category

untagged

Date

Dec 2024

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 50038-h-02-lzreceive-call-for-releaseoneid-results-in-oog-error-pa. Standalone Foundry PoC and full write-up: 50038-h-02-lzreceive-call-for-releaseoneid-results-in-oog-error-pa_exp in the evm-hack-registry mirror.


Vulnerability classes: dos-resistance · unbounded-loop · known-pattern

Reproduction: self-contained Foundry PoC with only forge-std — no fork. Full trace: output.txt. PoC: test/50038-h-02-lzreceive-call-for-releaseoneid-results-in-oog-error-pa_exp.sol.


Key info#

ImpactHIGH — destination lzReceive always OOGs under protocol gas options; messages stuck for costly retry
ProtocolNFTMirror — NFTShadow.getSendOptions / LayerZero path
Vulnerable codegetSendOptions: 80_000 + 20_000 * n underestimates mint (~46.7k+) per token
Bug classUnderfunded cross-chain execution gas / DoS
FindingPashov Audit Group · NFTMirror-security-review 2024-12-30 · H-02
Reportpashov/audits NFTMirror
SourceAuditVault
StatusAudit finding. Sample+extrapolate gas measurement in synthetic.
Compiler^0.8.24 (PoC)

TL;DR#

  1. releaseOnEid builds LZ options via getSendOptions(tokenIds).
  2. Budget = 80_000 + 20_000 * length — too low for destination mint/transfer (and worse with transfer validators).
  3. DVN lzReceive with that stipend OOGs; payload stored for retry.
  4. Every release path needs manual higher-gas retry.

The vulnerable code#

SOLIDITY
uint128 totalGasRequired =
    _BASE_OWNERSHIP_UPDATE_COST + (_INCREMENTAL_OWNERSHIP_UPDATE_COST * uint128(tokenIds.length));
// @> VULN: 20k/token << real mint/transfer cost (~46.7k+)

Fix: raise constants; allow user override of lzReceive gas.


Root cause#

Hard-coded gas options ignore real destination work (mint, transfer, optional validateTransfer).


Preconditions#

  • User releases shadow NFT(s) to another eid via releaseOnEid.
  • Destination handler performs mint/transfer under the computed stipend.

Attack walkthrough / PoC#

  1. Compute budget for 1 token → 100_000.
  2. Measure full-gas unlock cost for 1 token → exceeds budget.
  3. Call lzReceive with budget → fails / OOG; payloadHashes != 0 (retry queue).

Diagrams#

flowchart LR A["getSendOptions N=1"] --> B["budget = 100k"] C["Real mint cost"] --> D["sample >> 100k"] B --> E["lzReceive with budget"] D --> E E --> F["OOG / fail"] F --> G["payloadHashes stored for retry"]

Impact#

Cross-chain release is non-functional under default options; every message needs a higher-gas retry (cost + latency DoS).


Taxonomy#

  • genome: dos-resistance, unbounded-loop, known-pattern
  • severity/high · sector/bridge · sector/nft

Sources#


Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.