Solana and Rust program vulnerabilities in disclosed reports
Solana's account model moves the security burden from call ordering to account validation — the bugs look nothing like their EVM equivalents.
- Reports indexed
- 34
- Total paid
- $600k
- Critical
- 1
- Largest payout
- $600k
Solana programs are stateless code operating over accounts the caller supplies, which makes account validation the entire security boundary. The dominant bug class is a missing check: an account that is never verified as owned by the expected program, a signer flag never asserted, or a PDA derived without confirming the bump and seeds.
Type confusion is the second cluster. Two account structs with compatible layouts can be substituted for one another unless a discriminator is checked, which is precisely what Anchor's account discriminators exist to prevent — and precisely what raw programs and `AccountInfo` escape hatches skip.
Arithmetic differs too: Rust release builds wrap silently on overflow unless checked arithmetic is used explicitly, so the overflow class that Solidity 0.8 retired is still live here.
What reviewers look for
- Accounts used without an owner check against the expected program
- Missing `is_signer` assertions on authority accounts
- PDA derivation without canonical bump verification
- Type confusion between structurally similar account layouts
- Unchecked arithmetic in release builds
Curated highlights
The largest disclosed payouts in this group, with our own summary of each. Every report links back to the original disclosure.
Missing owner check on Solana instruction lets attacker drain vault
A missing program ownership check in a Solana yield vault protocol allowed unauthorized account spoofing during withdrawal processing. Because the instruction handler accepted an unchecked account and verified only a discriminator byte rather than validating account ownership, an attacker could supply a custom state account with manipulated fields. The protocol trusted the forged state data and generated a Program Derived Address (PDA) signature to execute a token transfer, enabling complete draining of the protocol's vault funds.
Dango DEX: Any change in bucket size will DOS order cancellation leading to frozen user funds and halts the entire auction
Dango's auction-based DEX tracks liquidity depth per bucket, keyed by the bucket sizes configured when an order is created. When an administrator later updates pair bucket sizes via a batch update, order cancellation and auction cleanup both call decrease_liquidity_depths(), which reloads the new bucket sizes and fails because DEPTHS.modify() requires the old storage keys to still exist. Every cancellation attempt reverts, permanently freezing user funds, and the same failure in cron::auction() cleanup trips the protocol's pause mechanism, halting all trading and cutting off LP and protocol revenue.
DODO Cross-Chain DEX: Bug in AccountEncoder causes wrong Solana account permissions
DODO's cross-chain bridge compresses Solana account permissions into a byte array in its EVM-side AccountEncoder library. The decompressAccounts() assembly routine reads a full 32-byte word with mload when parsing what should be a single 1-byte isWritable boolean, so the boolean evaluation consumes the boolean plus the first 31 bytes of the next account's public key. Because public keys are essentially always non-zero, every boolean collapses to true, marking all accounts writable regardless of user intent. This silently corrupts permission data for every multi-account Solana bridge transaction, expanding the attack surface where Solana programs may modify accounts users expected to be read-only. The finding was confirmed, rated medium (M-4), and fixed by extracting only the first byte via shr(248, ...).
DODO Cross-Chain DEX: Unauthorized Claim of Non-EVM Chain Refunds in `claimRefund` Function
DODO's omni-chain gateway contracts expose a claimRefund function that returns cross-chain refunds to their intended recipients. The authorization check determines the receiver by the stored walletAddress length: when the length is exactly 20 bytes (an EVM address) the caller must be that address, but for any other length the receiver simply becomes msg.sender, collapsing the guard into an always-true comparison. An attacker who watches for refund events carrying non-EVM addresses (e.g., Bitcoin) can front-run the legitimate claim and drain the contract of those funds at only gas cost. The supplied foundry test proves EVM refunds stay protected while a non-EVM refund is stolen, and the protocol addressed the flaw in a follow-up PR.
ZetaChain Cross-Chain: Solana zetaclient inbound observer is blocked indefinitely when an additional account is passed to the `call` instruction
The Solana gateway program's `call` instruction tolerates extra accounts in the instruction (it only needs one signer), but the Solana zetaclient's `ParseInboundAsCall` parser demands exactly one account via `len(instructionAccounts) != 1`. A single crafted transaction that appends an extra account executes successfully on-chain yet breaks parsing on the zetaclient observer, which re-scans the same failing signature every ticker and thereby stalls processing of all other inbound Solana transactions indefinitely. The protocol fixed the check to require at least one signer (`< 1`) in PR zeta-chain/node#3895.
ZetaChain Cross-Chain: Dirty EVM state changes are not committed before precompile calls, resulting in double-spending or loss of ZETA tokens
ZetaChain's EVM is implemented as ethermint on top of Cosmos SDK, and its staking precompiles execute Cosmos SDK logic (delegate, claim rewards) without first committing pending EVM statedb changes into the SDK state. SDK actions therefore read a stale balance, and when the EVM dirty state is committed at the end of the transaction it overwrites the SDK's freshly applied changes. This allows the same native ZETA to be transferred to another address in EVM and still delegated via the staking precompile (double spend), and in reward claims the freshly minted staking rewards are overwritten and lost. A complete e2e Go/Solidity PoC transfers a contract's full balance to 0xdead and then successfully stakes 2 ZETA to a validator, proving the double spend. The team acknowledged the issue but will not fix it at this time; the recommended mitigation (mirroring Evmos) is to flush EVM state before running SDK precompile code.
All reports in this group
- ZetaChain Cross-Chain: Token Mismatch in SPL Token DepositsLogic error$0
- ZetaChain Cross-Chain: Incorrect Formate for transaction building, and transaction executions In Solana will lead to revert all execute Operations.Bridge exploit$0
- ZetaChain Cross-Chain: Abort processing after call originating from Solana will cause loss of fundsLogic error$0
- ZetaChain Cross-Chain: Malicious observer can block messages added through the inbound trackerBridge exploit$0
- ZetaChain Cross-Chain: Malicious observer can drain Solana bridge by adding failed deposit transaction to inbound trackerBridge exploit$0
- ZetaChain Cross-Chain: The `sender` argument for the Solana `OnCall` instruction can be impersonatedAccess control$0
- ZetaChain Cross-Chain: Missing Nonce Reset During TSS Address Update Allowing Signature ReplayLogic error$0
- ZetaChain Cross-Chain: Outbound CCTXs to external chains are not properly validated, preventing subsequent outbounds to the external chain from being processedLogic error$0
- ZetaChain Cross-Chain: An attacker can stuff a Solana Outbound transaction with multiple instructions to block it from finalizingLogic error$0
- ZetaChain Cross-Chain: TON Gateway withdrawals can be DOS'd with an invalid withdrawal recipientLogic error$0
- Superposition: Tokens are pulled from users without verifying pool status contrary to requirementLogic error$0
- Orderly Solana Vault Contract: A malicious user can withdrawals another user's moneyAccess control$0
- Orderly Solana Vault Contract: [H-1]Access control$0
- WOOFi Swap on Solana: State changes are overwritten during anchor serialization when two accounts are the sameLogic error$0
- WOOFi Swap on Solana: Missing permission control in create_oracle and create_pool.Access control$0
- Lavarage: A borrower can borrow SOL without backing it by a collateralOracle manipulation$0
- Lavarage: Borrowers can avoid the payment of an interest share fee by setting themselves as a `fee_receipient`Logic error$0
- Lavarage: Innocent borrower could incur losses caused by a malicious lenderFront-running / MEV$0
- Lavarage: Lack of freeze authority check for collateral tokens on create trading poolAccess control$0
- Lavarage: Malicious borrowers will never repay loans with high interestLogic error$0
- Olas: Griefing attack on `liquidity_lockbox` withdrawals due to lack of minimum depositLogic error$0
- Olas: Wrong invocation of Whirpools's updateFeesAndRewards will cause it to always revertLogic error$0
- Olas: LP rewards in `liquidity_lockbox` can be arbitragedFlash loan attack$0
- Olas: Permanent DOS in `liquidity_lockbox` for under $10Logic error$0
- Olas: Missing slippage protection in `liquidity_lockbox::withdraw`Front-running / MEV$0
- Olas: Withdraw amount returned by `getLiquidityAmountsAndPositions` may be incorrectBridge exploit$0
- Olas: Possible DOS when withdrawing liquidity from Solana LockboxBridge exploit$0
- Olas: Withdrawals can be frozen by creating null depositsInteger overflow/underflow$0