Reproduced Exploit

Gondi — incorrect `_pendingWithdrawal` accounting in queueClaiming

1. queueClaimAll walks each queue's getTotalReceived and distributes into newer queues. 2. Distribution writes _pendingWithdrawal[secondIdx] = pendingForQueue. 3. A second older queue overwrites the same index instead of accumulating. 4. Earlier claimable amounts are lost after getTotalReceived is…

Apr 2024Otheruntagged3 min read

Chain

Other

Category

untagged

Date

Apr 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: 35211-h-09-incorrect-accounting-of-pendingwithdrawal-in-queueclai. Standalone Foundry PoC and full write-up: 35211-h-09-incorrect-accounting-of-pendingwithdrawal-in-queueclai_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/precision-loss · vuln/locked-funds · vuln/accounting

Reproduction: self-contained Foundry PoC with only forge-std — no fork, no RPC. Full trace: output.txt. PoC: test/35211-h-09-incorrect-accounting-of-pendingwithdrawal-in-queueclai.sol.


Key info#

ImpactHIGH — assignment (=) instead of += erases earlier queues' contributions to a newer queue's pending withdrawal; funds zeroed in getTotalReceived cannot be recovered
ProtocolGondi — ERC4626 lending pool withdrawal queues
Vulnerable codePool._updatePendingWithdrawalWithQueue: _pendingWithdrawal[secondIdx] = pendingForQueue
Bug classMissing accumulation in multi-queue distribution
FindingCode4rena — Gondi, 2024-04 · #35211 · reporter bin2chen
Reportcode4rena.com/reports/2024-04-gondi
SourceAuditVault
StatusAudit finding — confirmed; mitigated (missing +)
Compiler^0.8.24 (PoC)

TL;DR#

  1. queueClaimAll walks each queue's getTotalReceived and distributes into newer queues.
  2. Distribution writes _pendingWithdrawal[secondIdx] = pendingForQueue.
  3. A second older queue overwrites the same index instead of accumulating.
  4. Earlier claimable amounts are lost after getTotalReceived is cleared.

The vulnerable code#

SOLIDITY
// Pool._updatePendingWithdrawalWithQueue
uint256 pendingForQueue = totalReceived.mulDivDown(queueAccounting.thisQueueFraction, PRINCIPAL_PRECISION);
totalReceived -= pendingForQueue;

// @> VULN: assignment erases prior distributions into secondIdx
_pendingWithdrawal[secondIdx] = pendingForQueue;
// FIX: _pendingWithdrawal[secondIdx] += pendingForQueue;

Root cause#

Multi-source distribution into a shared pending array must accumulate. Using = keeps only the last source's contribution; prior sources are already zeroed in getTotalReceived, so the lost amount is permanent.

Attack walkthrough#

  1. Queue 0 and queue 1 each have getTotalReceived = 100e18.
  2. Queue 2 has a 50% thisQueueFraction (claimant).
  3. Processing queue 0 sets pending[2] = 50e18.
  4. Processing queue 1 sets pending[2] = 50e18 again (overwrite).
  5. Expected with +=: 100e18. Actual: 50e18. 50e18 lost.

Diagrams#

flowchart TD A["getTotalReceived 0 = 100"] --> B["distribute to queue 2"] B --> C["pending 2 = 50"] D["getTotalReceived 1 = 100"] --> E["distribute to queue 2"] E --> F["pending 2 = 50 OVERWRITE"] C --> F F --> G["lost 50 forever"]

Impact#

Withdrawal-queue depositors under-receive their pro-rata liquidation proceeds; erased amounts are unrecoverable after getTotalReceived is zeroed. Confirmed high; fixed by changing = to +=.

Taxonomy#

  • genome: precision-loss, locked-funds, liquidation-underwater, oracle-freshness
  • sector: lending, staking-pool
  • severity: high
  • platform: code4rena

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.