Status DataClose notification

Overlayer Disclosed Report

Cross-chain transfer lock via `totalBridgedOut` underflow on destination credit

Company
Created date
Apr 08 2026

Target

hidden

Vulnerability Details

OverlayerWrapCore tracks cross-chain outflow in totalBridgedOut and updates it in OFT hooks:

  • _debit(...) adds the burned/sent amount.
  • _credit(...) subtracts the received amount.

On a fresh destination chain, totalBridgedOut starts at 0. The first inbound credit executes:

totalBridgedOut -= amountReceivedLD;

If amountReceivedLD > 0, this underflows and reverts with panic 0x11.

Because the source-side debit has already happened in the send path, the destination receive fails while source balance is already reduced. This creates a transfer-lock condition in cross-chain flow.

Root cause and code references

  1. Counter definition and intended purpose:
  • contracts/overlayer/OverlayerWrapCore.sol:62
  • contracts/overlayer/OverlayerWrapCore.sol:63
  1. Source debit increments counter:
  • _debit(...) implementation starts at contracts/overlayer/OverlayerWrapCore.sol:510
  • increment at contracts/overlayer/OverlayerWrapCore.sol:527
  1. Destination credit decrements counter unconditionally:
  • _credit(...) implementation starts at contracts/overlayer/OverlayerWrapCore.sol:537
  • super._credit(...) at contracts/overlayer/OverlayerWrapCore.sol:542
  • unconditional subtraction at contracts/overlayer/OverlayerWrapCore.sol:543
  1. Non-hub chain deployments are expected by design:
  • hub-only collateral address validation note at contracts/overlayer/OverlayerWrapCore.sol:301
  • conditional hub check in init at contracts/overlayer/OverlayerWrapCore.sol:302
  1. Mint/redeem are hub-gated, but OFT credit is not:
  • onlyHubChain modifier definition at contracts/overlayer/OverlayerWrapCore.sol:77
  • mint path gated at contracts/overlayer/OverlayerWrapCore.sol:358
  • redeem path gated at contracts/overlayer/OverlayerWrapCore.sol:377
  1. Accounting dependency context (why this variable exists):
  • backing logic using totalSupply() + totalBridgedOut() at contracts/overlayerbacking/AaveHandler.sol:240
  • totalBridgedOut() read at contracts/overlayerbacking/AaveHandler.sol:241

Preconditions

  1. Cross-chain OFT send/receive is enabled between source and destination.
  2. Destination deployment has not previously executed outbound debit (fresh counter state).
  3. Incoming transfer amount is greater than zero.

Reproduction (clear steps)

  1. Deploy source and destination OverlayerWrapMock contracts using the same mock LayerZero endpoint.
  2. Configure source as hub-chain deployment and destination as non-hub deployment:
  • destination hubChainId: hubChainIdNum + 1 at test/Attack_CrossChainCreditUnderflow.ts:56
  1. Mint user balance on source.
  2. Perform source-side debit (simulates OFT burn/send):
  • source.testDebit(...) at test/Attack_CrossChainCreditUnderflow.ts:82
  1. Confirm source accounting moved:
  • source.totalBridgedOut() == bridgeAmount at test/Attack_CrossChainCreditUnderflow.ts:85
  1. Perform destination-side credit (simulates OFT receive/mint):
  • destination.testCredit(...) at test/Attack_CrossChainCreditUnderflow.ts:90
  1. Observe revert:
  • revertedWithPanic(0x11) at test/Attack_CrossChainCreditUnderflow.ts:91
  1. Effect: source was debited, destination remains uncredited.

Validation steps

  1. Execute the dedicated PoC test:
CI=1 HARDHAT_DISABLE_TELEMETRY_PROMPT=true npx hardhat test --config hardhat.config.poc.ts test/Attack_CrossChainCreditUnderflow.ts
  1. Confirm output contains:
Attack PoC: Cross-chain credit underflow
  ✔ burns on source but reverts on first destination credit

1 passing
  1. static validation checklist:
  • _debit increments totalBridgedOut at contracts/overlayer/OverlayerWrapCore.sol:527
  • _credit decrements totalBridgedOut at contracts/overlayer/OverlayerWrapCore.sol:543
  • no guard preventing underflow in _credit
  • PoC assertion expects panic 0x11 at test/Attack_CrossChainCreditUnderflow.ts:91
  1. Why this validates quickly:
  • the PoC calls exposed internal hooks directly via OverlayerWrapMock:
    • testDebit at contracts/test/OverlayerWrapMock.sol:10
    • testCredit at contracts/test/OverlayerWrapMock.sol:19
  • this isolates only the vulnerable accounting branch and proves deterministic revert behavior.

Supporting files / PoC

  1. Core vulnerable logic:
  • contracts/overlayer/OverlayerWrapCore.sol:510
  • contracts/overlayer/OverlayerWrapCore.sol:527
  • contracts/overlayer/OverlayerWrapCore.sol:537
  • contracts/overlayer/OverlayerWrapCore.sol:542
  • contracts/overlayer/OverlayerWrapCore.sol:543
  1. Hub/non-hub deployment context:
  • contracts/overlayer/OverlayerWrapCore.sol:301
  • contracts/overlayer/OverlayerWrapCore.sol:302
  • contracts/overlayer/OverlayerWrapCore.sol:358
  • contracts/overlayer/OverlayerWrapCore.sol:377
  1. Backing/accounting dependency context:
  • contracts/overlayerbacking/AaveHandler.sol:239
  • contracts/overlayerbacking/AaveHandler.sol:240
  • contracts/overlayerbacking/AaveHandler.sol:241
  1. PoC harness and test:
  • contracts/test/OverlayerWrapMock.sol:10
  • contracts/test/OverlayerWrapMock.sol:19
  • test/Attack_CrossChainCreditUnderflow.ts:56
  • test/Attack_CrossChainCreditUnderflow.ts:62
  • test/Attack_CrossChainCreditUnderflow.ts:82
  • test/Attack_CrossChainCreditUnderflow.ts:85
  • test/Attack_CrossChainCreditUnderflow.ts:90
  • test/Attack_CrossChainCreditUnderflow.ts:91
  1. Test configuration used for reproducibility:
  • hardhat.config.poc.ts

Attachments

hidden
CommentsReport History
Comments on this report are hidden
Details
Statedisclosed
Severity
Critical
Bounty$69
Visibilitypartially
VulnerabilityInteger Underflow
Participants
hidden