Security research
Solana vulnerabilities: account checks, CPIs and arithmetic
Solana security reviews must validate the accounts an instruction receives and the authority to use them. The program must enforce application rules, such as which vault belongs to a user, alongside memory safety and runtime checks.
Account ownership, authority and signatures answer different questions
In Solana's account structure, the owner field identifies the program that can modify account data. It does not identify the user authorized to withdraw from a vault. An application may store that user's authority in the account's data. Validate both the owning program and the expected data layout before relying on the stored fields.
A signer flag establishes that the account has signing privilege for the instruction. It does not establish that the signer is the authority named by the application. A withdrawal should verify the relationship between the signer, stored authority and selected vault.
A well-formed public key can still identify the wrong account.
| Question | Evidence to check | Example rejection |
|---|---|---|
| Is this application state? | Owner program and decoded account type | State supplied from a different program |
| Is this the permitted authority? | Authority key equals the stored authority | A different user's valid signer |
| Did the authority authorize this invocation? | Required signer privilege | The correct key supplied without signing |
| Is this the intended vault? | Expected address and application relationships | Another vault with otherwise valid data |
A local model tests 16 combinations
We wrote a small Rust model with four checks: owner program, authority identity, signer flag and vault identity. Each check can be true or false, so the test enumerates all 16 combinations. With a balance of 10 and a requested withdrawal of 3, one combination passes and returns 7. The remaining 15 reject. Three additional assertions check withdrawal amounts of zero, the entire balance and more than the available balance.
if a.owner_program != a.expected_program { return Err("owner"); }
if a.authority_key != a.stored_authority { return Err("authority"); }
if !a.is_signer { return Err("signature"); }
if a.vault_key != a.expected_vault { return Err("vault"); }
a.balance.checked_sub(amount).ok_or("balance")
The model uses byte-sized placeholders for account keys. It does not parse Solana accounts, derive PDAs or execute a transaction. Its purpose is to make the four independent requirements visible.
A passing result proves only that this local model enforces its written checks on the specified inputs.
To reproduce it, download the Rust source and follow the run instructions. The recorded run used rustc 1.86.0. The CSV contains all 16 rows and reports the first rejection reason in each failing row.
rustc account_checks.rs -o /tmp/defisec-account-checks
/tmp/defisec-account-checks > results.csvValidate account relationships before a CPI
A program-derived address is derived from a program ID and seeds. The seeds must encode the relationship the application expects. Deriving a valid PDA using caller-selected inputs does not by itself establish that it belongs to the intended user or market. Compare the derived address with the supplied account and validate the state that connects them.
For token operations, review the expected token program, mint and token-account authority together. Check whether account roles are allowed to share an address; accepting the same account in two roles can invalidate assumptions about separate balances. Record these constraints explicitly in instruction tests.
The Solana CPI documentation explains that signer and writable privileges pass from caller to callee, while a callee cannot escalate beyond the privileges passed to it. invoke_signed adds PDA signing through the supplied seeds. Before making a CPI, validate the destination program and the account set to which the program is forwarding authority.
Correct the reentrancy and resource-limit assumptions
Solana allows direct self-recursion such as A → A. Indirect reentrancy such as A → B → A is rejected with ReentrancyNotAllowed, according to the current CPI documentation. The earlier article mixed a description of permitted self-recursion with a blanket dismissal of reentrancy attacks. Review permitted call paths and application state transitions using these specific runtime rules.
The earlier article also described a block compute figure as a transaction limit. Use the documentation for the exact resource being constrained and the target runtime's active features. CPI stack depth, transaction compute and block capacity are separate limits. A feature-dependent constant should not become an undated claim about every cluster.
For a practical review, measure the longest expected instruction path under the configured compute budget. Test large valid account collections and boundary-sized inputs. Record the runtime version and feature configuration with the result so that a later upgrade can trigger a meaningful regression check.
Test arithmetic and application invariants
Rust's memory-safety guarantees do not establish correct withdrawal permissions or economic calculations. Use explicit checked operations where a rejected arithmetic operation is required. The local example uses checked_sub so an excessive withdrawal returns an error independently of build settings.
For protocol arithmetic, test rounding direction, decimal conversions, zero denominators and boundary values in the chosen integer type. Write the expected invariant first: a withdrawal must not exceed the authorized balance, for example. Then include cases that exercise each rejected condition separately, as well as combinations that might bypass an earlier check.
Move from small models to instruction-level tests with Mollusk or LiteSVM, and explore stateful sequences with Trident. Match each tool's supported environment to the program under review. The review methodology describes how to retain test evidence and unresolved limitations alongside findings.
The Solana instruction testing tools guide compares the inputs and evidence these workflows produce.
Sources
Checked . Local examples include their inputs and limits.
About this revision
Clarified account ownership and signing authority, corrected reentrancy and compute-limit claims, and added a local validation model. The original contribution was attributed to 0xGuard; this DeFiSec editorial revision is not a new review by that contributor.
Suggest a correction with evidence