Reproduced Exploit
Karak NativeVault: `activeValidatorCount` is never incremented, bricking every snapshot
Chain
Other
Category
untagged
Date
Jan 1970
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
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.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: Karak-security-review-June. The historical source/toolchain is unavailable; this entry is documentation only and claims no executable Forge PoC.
Vulnerability classes: vuln/dos · vuln/arithmetic
Reproduction: a faithful minimal reproduction of the vulnerable finding — the
validateWithdrawalCredentialsregistration tail and the snapshot machinery (_startSnapshot/validateSnapshotProofs/_updateSnapshot) are reproduced verbatim (missing increment marked@>) with faithful minimal doubles; local deploy, no fork.
Root cause#
In NativeVaultLib.validateWithdrawalCredentials, a validator is registered as ACTIVE with its restakedBalanceWei set and written back to storage — but ownerToNode[nodeOwner].activeValidatorCount is never set or increased. The counter is only ever decremented (in validateSnapshotProof when a validator's beacon balance hits 0), so it starts and stays at 0. The verbatim registration tail, with the missing increment marked:
validatorDetails.status = NativeVaultLib.ValidatorStatus.ACTIVE;
validatorDetails.validatorIndex = validatorFieldsProof.validatorProof.validatorIndex;
validatorDetails.lastBalanceUpdateTimestamp = updateTimestamp;
validatorDetails.restakedBalanceWei = restakedBalanceWei;
@> self.ownerToNode[nodeOwner].validatorPubkeyHashToDetails[validatorPubkeyHash] = validatorDetails;
// missing: self.ownerToNode[nodeOwner].activeValidatorCount++;
Because activeValidatorCount stays 0, _startSnapshot seeds remainingProofs = node.activeValidatorCount = 0, and the verbatim snapshot.remainingProofs--; in validateSnapshotProofs underflows on the very first submitted proof (Solidity 0.8 checked arithmetic → Panic(0x11) revert).
Why it's exploitable here#
A node that has registered a real validator can therefore never complete a balance snapshot:
- A node owner registers one validator with 32 ETH restaked through
validateWithdrawalCredentials. The bug leavesactiveValidatorCount == 0even though a live validator now backs the node's consensus balance. startSnapshotbuilds aSnapshotwithremainingProofs = 0, then setscurrentSnapshotTimestamp— the node is now mid-snapshot.- The owner submits the validator's balance proof via
validateSnapshotProofs. The loop runssnapshot.remainingProofs--on0, underflowing →Panic(0x11)revert. - Every subsequent attempt reverts identically, and the node is stuck with
currentSnapshotTimestampset. The 32 ETH of restaked balance can never be finalized or credited — the node's restaked-ETH accounting is permanently bricked (DoS). A control node using the one-line-fixed path (activeValidatorCount == 1 → remainingProofs == 1) completes its snapshot, proving the revert is caused precisely by the missing increment.
Attack path#
Marked-line walkthrough (Playground)#
The EVM Playground pins each step to the exact executed source line in 0x671d353a…:
- L94 — Read the empty validator record: Setup: validateWithdrawalCredentials loads the node's blank ValidatorDetails into memory before marking the validator ACTIVE.
- L101 — Counter never incremented on registration: Root cause: the active validator is written back to storage but
activeValidatorCount++is missing, so the node's counter stays at 0. - L136 — Load validator for the balance proof: During a snapshot, validateSnapshotProof reads the validator's details to compute its beacon-balance delta against the restaked amount.
- L191 — Entry point registers withdrawal credentials: Setup: the public validateWithdrawalCredentials takes the validator pubkey hash and restaked balance, delegating to the buggy library function.
- L234 — Snapshot created with zero proofs: startSnapshot emits SnapshotCreated after seeding remainingProofs from activeValidatorCount — which the bug left at 0 despite a live validator.
- L260 — Finalize the snapshot: _updateSnapshot runs after the proof loop to decide whether the snapshot is complete and the node's restaked ETH should be credited.
- L275 — Credit branch fires on zero remaining: Because remainingProofs is 0, _updateSnapshot takes the credit branch and emits SnapshotFinished, letting creditedNodeETH update without any valid proof.
- L304 — Record the frozen restaked ETH: The harness mints the victim's 32 ETH of unfinalizable restaked balance to the SINK marker, quantifying the funds permanently frozen by the DoS.
PoC#
Registry (Foundry, local deploy — verbatim vulnerable source + harm-asserting test):
cd 38491-h-03-activevalidatorcount-is-never-set-or-increased-pashov-a_exp && forge test -vvv
The browser Playground replays the same synthetic opcode-for-opcode and measures the harm: a node registers a 32 ETH validator, activeValidatorCount stays 0, startSnapshot seeds remainingProofs = 0, and the first validateSnapshotProofs reverts with an arithmetic underflow (Panic 0x11) — the node can never finalize its snapshot, permanently freezing the restaked ETH. Both gates are green (registry forge test PASS + Playground _verify-poc VERDICT: PASS).
Sources & further analysis#
Reproductions & code
- No executable Forge reproduction is claimed; the historical source/toolchain was unavailable for this finding.
- AuditVault finding: Karak-security-review-June.
- Upstream DeFiHackLabs PoC directory: src/test.
Alerts & third-party analyses
- DeFiHackLabs incident explorer: search "Karak NativeVault:
activeValidatorCountis never incremented, bricking every snapshot". - 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.