Centrifuge Protocol V3.1 — disclosed vulnerability reports and payouts
Every publicly disclosed and closed bug bounty report we hold for Centrifuge Protocol V3.1, with our own summary of each finding and a link to the original disclosure.
- Reports indexed
- 10
- Total paid
- $0
- Critical
- 0
- Largest payout
- $0
This page collects the 10 closed, publicly disclosed Centrifuge Protocol V3.1 reports indexed on CoinBuggie. Nothing here is active or unpatched — every entry was published by the programme or the researcher after remediation.
The findings concentrate in Reentrancy, Oracle manipulation, Bridge exploit, Access control, Logic error, across EVM-Solidity. Reading a single protocol end to end is the fastest way to see which assumptions its codebase repeatedly gets wrong, which is usually a better predictor of where the next finding lives than the category alone.
Use the vulnerability class links below to compare these findings against the same bug class in other protocols.
What reviewers look for
- Reentrancy findings disclosed against Centrifuge Protocol V3.1
- Oracle manipulation findings disclosed against Centrifuge Protocol V3.1
- Bridge exploit findings disclosed against Centrifuge Protocol V3.1
- Access control findings disclosed against Centrifuge Protocol V3.1
- Logic error findings disclosed against Centrifuge Protocol V3.1
Curated highlights
The largest disclosed payouts in this group, with our own summary of each. Every report links back to the original disclosure.
Centrifuge Protocol V3.1: Malicious adapters can exploit message batching via adapter-side reentrancy to cause message loss for any other pool
Centrifuge's Gateway._endBatching() snapshots its batch locators into memory, immediately clears the transient storage array, and then loops over that stale in-memory snapshot to dispatch each batch through external adapter calls. Because isBatching remains true for the whole loop and there is no reentrancy guard, a malicious adapter can reenter during the send loop and issue a nested withBatch()/queueManager.sync() that appends fresh accounting messages into the already-cleared transient slot. Those newly created locators are never processed, and since transient storage is wiped when the transaction ends, the critical messages are permanently destroyed. This lets a pool manager the attacker controls silently drop another pool's accounting synchronization messages, producing unrecoverable hub/spoke accounting divergence and blocking user claims, transfers, and subsequent nonce-ordered updates.
Centrifuge Protocol V3.1: `SimplePriceManager.onUpdate()` lack of forward execution fee leads dependent functions to revert
Centrifuge's SimplePriceManager.onUpdate() routes share-price updates through Gateway.withBatch(), but the function is not payable and forwards no native token, so the outgoing batch runs with zero execution fuel. When the protocol is not in unpaidMode, Gateway._send() cannot satisfy fuel >= cost and reverts with NotEnoughGas. This permanently DoSes NAV and oracle price propagation for any caller using NAVManager.updateHoldingValuation() or OracleValuation.setPrice(). The fix makes onUpdate() payable and forwards msg.value to withBatch().
Centrifuge Protocol V3.1: Stranded ETH on batched `crosschainTransferShares` call
Centrifuge's VaultRouter exposes a payable crosschainTransferShares function that silently zeroes out msg.value whenever the gateway is in batching mode. In that batched path the gateway is already funded separately, so the router neither forwards nor refunds the attached ETH, leaving it stranded in the router balance with only a privileged, attribution-free sweep as recovery. The condition is reachable through a normal deposit-plus-bridge batch flow, making it a repeatable loss path for users who attach value to what appears to be a benign call. The accepted fix adds an ETHNotAcceptedInBatch guard that reverts whenever batching is active and value is attached, so no ETH can be silently trapped.
Centrifuge Protocol V3.1: Gas engineering during `adapter` execution can be used to maliciously split critical message batches
Centrifuge's AxelarAdapter.execute() is permissionless and forwards caller-supplied gas straight into Gateway.handle(), which processes each batched cross-chain message inside an isolate try-catch that stores failed messages in failedMessages rather than reverting the batch. An attacker can therefore execute a correctly batched approve+issue or approve+revoke callback pair with a targeted gas amount that completes only the first message while the second reverts, breaking the protocol's required atomicity. For sync vaults, splitting approve+revoke leaves fewer assets backing the same share count, transiently depressing the share price so the attacker can deposit at the corrupted price and dilute existing holders. The finding was acknowledged by the team but will not be fixed at this time.
Centrifuge Protocol V3.1: Prices computed in SimplePriceManager is off even after `BatchRequestManager#revokeShares()` is called
Centrifuge Protocol V3.1's share-price computation becomes skewed after a share revocation. When shares are burned on a spoke chain via revokeShares(), the share supply drops immediately but the corresponding assets are only reserved and remain counted in the balance sheet, so the net asset value feeding SimplePriceManager's pricePoolPerShare does not fall in step with the reduced supply, producing an inflated price. Because sync() is permissionless and the newly notified price is usable right away by eligible minters of a sync vault, a user minting at that moment pays more assets for fewer shares. The issue was confirmed as medium severity and rectified by withdrawing redeemed assets to globalEscrow at the time of revocation so NAV and supply move together.
Centrifuge Protocol V3.1: Inadequate gas reservation in `Gateway.handle()` try-catch block enables permanent DOS via batch-level gas exhaustion
Centrifuge's Gateway.handle() processes batched cross-chain messages in a loop but reserves only GAS_FAIL_MESSAGE_STORAGE (40k gas) for the current message via gasleft() - 40k, ignoring the gas needs of all remaining messages. Because a permissionless updateContract() lets anyone attach malicious gas-wasting UntrustedContractUpdate messages into a batch alongside critical system messages like UpdateHoldingAmount, an attacker can drain the forwarded gas so the batch reverts at the NotEnoughGasToProcess() check before critical messages ever run. The report's gas math shows that even with a full 30M-gas block, just two to three malicious messages exhaust the reservation, permanently bricking critical pool-accounting and share-transfer messages.
All reports in this group
- Centrifuge Protocol V3.1: `Gateway.withBatch()` lacks message count validation enabling permanent DOS via an excessive number of messages in a batchBridge exploit$0
- Centrifuge Protocol V3.1: `MessageProcessor` fails to disable `unpaidMode` during `UntrustedContractUpdate` execution enabling permanent DOS via malicious unpayable batch creationLogic error$0
- Centrifuge Protocol V3.1: Pool managers can steal all other pools' pending deposits from `globalEscrow` via malicious `requestManager` swappingAccess control$0
- Centrifuge Protocol V3.1: Incorrectly handled subtraction leads to underflow resulting in permanent user fund lock and DOSInteger overflow/underflow$0