TaraClient finalizeBlocks Quorum-Bypass via In-Tx Validator Set Mutation
Exploit Transactions
0xa1301596c77938cb31cbd282da79f6499f23cd8ffff5e609a77216ea1cf040a4Victim Addresses
0xcdf14446f78ea7ebcaa62fdb0584e4d2e536b999Ethereum0x359cf536b1fd6248ebad1449e1b3727cab33a01dEthereumLoss Breakdown
Similar Incidents
Tara Light Client Quorum-Check Ordering Bug Enables Forged Bridge Finalization and WETH Drain
44%C87C Router tokenReceived Authentication Bypass Drains Victim-Approved USDT
31%LiteV3 Bridge Aggregator Proxy Initialization Race Enabled Unauthorized UUPS Takeover
30%BUILD Governance Takeover and Unlimited Mint ACT Exploit
29%Audius Governance Reinitialization and Treasury AUDIO Drain
28%Beanstalk flash-loan governance takeover drains treasury assets
28%Root Cause Analysis
TaraClient finalizeBlocks Quorum-Bypass via In-Tx Validator Set Mutation
1. Incident Overview TL;DR
A single Ethereum mainnet transaction (0xa1301596c77938cb31cbd282da79f6499f23cd8ffff5e609a77216ea1cf040a4, block 24513601) executed a full exploit chain against TaraClient/Tara Bridge: validator-set takeover, malicious block finalization, connector state application, swaps, and ETH profit extraction.
The root cause is a code-ordering flaw in finalizeBlocks on TaraClient implementation 0x34eae07195d83305a0be4786f2ab8705613c69c0 (proxy 0xcdf14446f78ea7ebcaa62fdb0584e4d2e536b999). Attacker-controlled validator updates are applied before quorum/signature-weight validation, so authorization is checked against in-transaction mutated state rather than the pre-existing validator set.
This is an ACT-class exploit (is_act=true): an unprivileged EOA can submit the same class of payload and realize profit without privileged keys.
2. Key Background
- TaraClient stores validator voting weights in
validatorVoteCounts(mapping slot base8) and aggregate voting power intotalWeight(slot9). finalizeBlocksselector is0x5d0d5734; signature-weight helper isgetSignaturesWeight(bytes32,(bytes32,bytes32)[])selector0x97f8e2f0.- Bridge state application is invoked through bridge proxy
0x359cf536b1fd6248ebad1449e1b3727cab33a01dselector0x6cd50a67, which then calls connectorapplyState(bytes)(0x9bd15c9e). - In the seed exploit transaction, the attacker EOA is
0x7bd736631afbe1d3795a94f60574f7fa0ae89347; two transient helper contracts are created (0xddf10e...,0xfc99fa...) and self-destructed after execution.
3. Vulnerability Analysis & Root Cause Summary
Root-cause category is ATTACK. The broken invariant is: signature quorum for block finalization must be computed from the validator set that existed before applying any validator updates in that same call. In the vulnerable flow, the contract first writes attacker-supplied validator updates and totalWeight, then computes signature weight/quorum.
Disassembly evidence shows finalizeBlocks jumps to update routine 0x0db3 before jumping to signature-weight routine 0x0887. The update routine performs SSTORE to the validator mapping and totalWeight; the signature routine later does SLOAD from the validator mapping for recovered signers. As a result, a signature that has zero pre-state voting power is counted as high-weight after attacker-controlled writes execute.
This enables unauthorized finalization of attacker-chosen bridge roots and downstream bridge-state execution, violating finality/security assumptions and enabling asset extraction.
4. Detailed Root Cause Analysis
4.1 Exploit pre-state and payload
Before the exploit transaction:
totalWeight = 1,584,486.validatorVoteCounts[0x7bd736631afbe1d3795a94f60574f7fa0ae89347] = 0.
The attacker-crafted finalizeBlocks payload includes a validator update assigning weight 2147483647 to the attacker address and uses one compact signature tuple.
Seed finalize payload (decoded):
validator update: (0x7bd736631afbe1d3795a94f60574f7fa0ae89347, 2147483647)
nonce: 572
new_hash: 0x966ab46259230d719c7ffc5a73688b23f230f16521b11115efa5eb544b115222
bridge_root: 0x7759b67c3728c3cf1d79dc2f15b42ea903c349029b8bd189406abc5c5198b433
4.2 Code-level breakpoint (ordering bug)
cast_disassembly.txt for implementation 0x34ea... shows the critical execution order:
000005e2: PUSH2 0x0db3
000005e5: JUMP ; enter validator update routine first
...
00000660: PUSH2 0x0887
00000663: JUMP ; enter signature-weight/quorum routine after updates
00000f02: SSTORE ; write validatorVoteCounts[...] (mapping slot base 8)
00000f1e: SSTORE ; write totalWeight (slot 9)
...
00000a5d: SLOAD ; read validatorVoteCounts[...] during signature weighting
This establishes the invariant break at code level: authorization depends on state that the same attacker-controlled call has just mutated.
4.3 Independent runtime/state confirmation
Signature-weight checks around inclusion prove the effect is not theoretical:
preblock_getSignaturesWeight(...seed_signature) = 0x0
postblock_getSignaturesWeight(...seed_signature) = 0x7fffffff
Negative checks also prove quorum is based on mutated totalWeight:
empty signatures revert -> custom error 0xb7842ce0 arg0=0x400c16b3 arg1=0x0
low weight update revert -> custom error 0xb7842ce0 arg0=0x000c16b4 arg1=0x0
Slot-level state diff for 0xcdf... in the seed tx confirms in-call mutation:
{
"validator_slot": {
"slot": "0xd112448c754d9230d04a6b39cef14b6a8307790cdd38c7db3617572e79e370c0",
"value": "0x000000000000000000000000000000000000000000000000000000007fffffff"
},
"totalWeight_slot_9": {
"before": "0x0000000000000000000000000000000000000000000000000000000000182d66",
"after": "0x0000000000000000000000000000000000000000000000000000000080182d65"
}
}
4.4 Full exploit consequence
After unauthorized finalization, execution immediately proceeds through bridge state application (0x359c...::0x6cd50a67) and connector applyState(bytes) calls to release assets into attacker runtime logic, followed by swaps and ETH extraction to the attacker EOA.
5. Adversary Flow Analysis
End-to-end adversary execution in one transaction:
- Deploy helper/orchestrator contracts.
- Call
TaraClient.finalizeBlockswith attacker-controlled validator update and signature tuple. - Contract applies validator update and inflated
totalWeight, then validates signatures/quorum against mutated state, and finalizes attacker-chosen root. - Call bridge entrypoint
0x6cd50a67; bridge triggers connectorapplyState(bytes)calls:0x2b5ec5c4...(ETH path),0x950bcda6...(USDT path),0xff235ea7...(TARA path).
- Route extracted assets through swap venues (
0x00000000008892..., Uniswap V3 pool0xc7bbec68..., UniV4 adapter0x5745050e..., DPP clone0x0ecef178...). - Pay 0.01 ETH to
0xdadb0d80178819f2319190d340ce9a924f783711. - Self-destruct helper contracts and return residual ETH to attacker EOA.
Representative call-tree excerpt:
CALL ... -> 0xcdf14446... selector 0x5d0d5734
DELEGATE 0xcdf14446... -> 0x34eae071... selector 0x5d0d5734
CALL ... -> 0x359cf536... selector 0x6cd50a67
CALL 0x359cf536... -> 0x2b5ec5c4... selector 0x9bd15c9e
CALL 0x359cf536... -> 0x950bcda6... selector 0x9bd15c9e
CALL 0x359cf536... -> 0xff235ea7... selector 0x9bd15c9e
SELFDESTRUCT 0xfc99fa4b... -> 0xddf10e09...
SELFDESTRUCT 0xddf10e09... -> 0x7bd73663...
Adversary-related accounts identified:
0x7bd736631afbe1d3795a94f60574f7fa0ae89347(EOA sender and net recipient)0xddf10e090791369024cfa51c3df1192bfeda3eae(created helper)0xfc99fa4b30bdb2e3da90706882a24759307b0343(runtime executor)
Victim protocol components:
- TaraClient proxy
0xcdf14446f78ea7ebcaa62fdb0584e4d2e536b999 - Bridge proxy
0x359cf536b1fd6248ebad1449e1b3727cab33a01d
6. Impact & Losses
Measured impact from collected balance/log artifacts:
- Unauthorized consensus/finality break: attacker finalized bridge root
0x7759b67c...at nonce572. - Unauthorized bridge-state application triggered connector transfers and token mint/flows.
- Reported extracted amounts:
- ETH:
4.336484040293252284(connector-path extraction) - USDT:
5,958,440,844 - TARA:
1e50minted into attacker path before swaps
- ETH:
- Net attacker EOA native delta in seed tx:
+7.381999992558813941 ETH(post gas and explicit 0.01 ETH builder payment included in net state delta accounting).
7. References
- Seed transaction metadata and tx payload:
0xa1301596c77938cb31cbd282da79f6499f23cd8ffff5e609a77216ea1cf040a4. - Slot-level state diff for validator/weight mutation on TaraClient proxy
0xcdf14446.... finalizeBlocksreplay trace showing successful path and recovered signer.- Negative replay traces showing quorum reverts for empty signatures and low-weight mutation.
- Signature-weight pre/post checks (
0to0x7fffffff) for the same compact signature. - Disassembly of implementation
0x34eae071...showing jump-to-0x0db3before jump-to-0x0887and associatedSSTORE/SLOADpoints. - Seed call-tree and receipt logs showing bridge/connector execution and extraction flow.
- Seed balance-diff artifact showing final ETH deltas and builder payment account.