Hello! Welcome to the first lesson in our module on "Auditing and Optimizing Solana Programs."
In the previous modules, we've focused on building and managing the lifecycle of a Solana program. You've learned how to structure state, implement logic, upgrade code, and even migrate on-chain data. We've built the "happy path." Now, we pivot to a crucial discipline: security. In the world of blockchain, where programs can control millions of dollars in assets, a single vulnerability can be catastrophic.
This lesson will introduce you to the fundamental mindset of a security auditor. We'll explore why the Solana programming model requires you to be paranoid and then dive into three of the most common and dangerous categories of vulnerabilities: improper authority checks, account confusion, and integer overflows. Mastering the ability to spot these issues is the first step toward writing truly robust and secure smart contracts.
The Attacker Mindset: Trust Nothing
In traditional web development, you often have a trusted backend environment. You control the server, the database, and the APIs. While you must always sanitize user input from the frontend, you have a high degree of control over the execution environment.
On Solana, this assumption is inverted. The programming model is inherently attacker-controlled. A transaction instruction is created entirely on the client side, and the client specifies all the accounts and data that will be passed to your program. A malicious user can pass in any account they want, in any order they want, with any data they want.
Your program's sole defense is to be deeply skeptical of every piece of information it receives. It must rigorously validate every account and every parameter before acting on them.
A Hitchhiker's Guide to Solana Program Security
To build a solid foundation for security, let's start with a guide from Helius that expertly explains this security-first perspective.
Please read the section 'The Attacker Mindset in Exploiting Solana Programs'. Focus on internalizing these key ideas: Why Solana's model is described as 'attacker-controlled'. The list of 'Potential Attack Vectors', as this gives you a map of what we'll be looking for. The high-level 'Mitigation Strategies', which emphasize validation and best practices.
Vulnerability 1: Improper Authority Checks
This is arguably the most common and critical class of vulnerabilities in Solana programs. It stems from failing to properly verify that an action is authorized. This broad category can be broken down into two frequent mistakes.
Missing Signer Check
A program must verify that an account meant to authorize a privileged action has actually signed the transaction. Simply passing the correct public key is not enough. Anyone can pass anyone else's public key as an account. The is_signer flag on an AccountInfo struct is the definitive proof of authorization.

Missing Ownership Check
Every account on Solana has an owner field, which specifies the program that is allowed to modify its data. When your program receives an account that is supposed to hold its state (like a configuration or user profile account), it must verify that the account's owner is your program's own ID.
If you skip this check, an attacker can create their own fake configuration account, set themselves as the admin within it, and pass it to your instruction. Your program would read the fake data and grant them unauthorized privileges.
A Hitchhiker's Guide to Solana Program Security
The Helius guide provides excellent, concise explanations and code examples for these two authority check vulnerabilities.
Now, read the sections 'Missing Ownership Check' and 'Missing Signer Check'. For each section, focus on: The 'Vulnerability' description. The insecure code in the 'Example Scenario'. Understand why it's vulnerable. The 'Recommended Mitigation' showing the explicit check (if !admin.is_signer or if config.owner != program_id). Note how Anchor's Signer<'info> and Account<'info, T> types handle these checks for you automatically, which is a major security benefit of the framework.
Vulnerability 2: Account Confusion (Type Cosplay)
Account confusion, also known as "type cosplay," occurs when a program is tricked into interpreting an account of one type as if it were another. This can happen if a program deserializes account data without verifying what type of data it's supposed to be.

For example, imagine two account structs: UserProfile and Vault. If both start with a Pubkey field named authority, an attacker might be able to pass a UserProfile account to an instruction that expects a Vault account. If the instruction only checks that the authority is a signer, the attacker could use their UserProfile account to drain the Vault.
This is why Anchor automatically prepends an 8-byte discriminator (a hash of the struct's name) to all account data. When you use Account<'info, MyStruct>, Anchor first checks that the first 8 bytes of the account data match MyStruct's unique discriminator before proceeding. This makes type cosplay attacks impossible if you use Anchor's account wrappers correctly.
A Hitchhiker's Guide to Solana Program Security
Let's return to the Helius guide to see a concrete example of this attack and its mitigation.
Please read the section titled 'Type Cosplay'. Pay attention to how the vulnerability is created by deserializing raw data without a type check, and how the mitigation involves adding a discriminant field to the struct.
Vulnerability 3: Integer Overflow and Underflow
Integer overflow/underflow is a classic computer science bug that becomes extremely dangerous in the context of smart contracts. An overflow happens when an arithmetic operation's result exceeds the maximum value for its integer type (e.g., u64), causing it to "wrap around" to a small number. An underflow is the opposite, where subtracting from a small number wraps it around to a huge one.
This can be exploited to manipulate token balances, bypass checks, or cause other unintended financial calculations.
What is an Integer Overflow Vulnerability? | Hacking 101
Before we look at the Solana context, let's get a clear, visual explanation of what an integer overflow is. This video by Marcus Hutchins is a fantastic introduction.
Watch the first four and a half minutes of the video (00:00 - 04:53). Focus on understanding: How adding to the maximum value of a binary integer causes it to wrap around to zero. The real-world example with the Berkshire Hathaway stock price. How this can become a security vulnerability (the heap overflow example shows how a miscalculation can lead to a critical bug).
Now for the critical Solana context: Rust's compiler, by default, disables overflow checks when compiling in release mode. The Solana toolchain command, cargo build-bpf, compiles your program in release mode. This means your deployed Solana program is vulnerable to silent integer overflows and underflows unless you take specific steps to prevent them.
A Hitchhiker's Guide to Solana Program Security
The Helius guide details this Solana-specific 'gotcha' and explains the standard ways to mitigate it.
Read the section 'Overflow and Underflow'. Make sure you understand the two primary mitigation strategies: Enabling overflow-checks in your Cargo.toml file. This is the simplest solution but adds to compute cost. Using Rust's checked_* arithmetic methods (e.g., checked_add, checked_sub). This gives you fine-grained control and is often the preferred method for performance-critical code.
Test your understanding!
You are reviewing a function in a native Solana program that allows a user to withdraw tokens from their balance.
// The user's balance is stored in the first 8 bytes of `user_balance_account`.
// `withdraw_amount` comes from instruction data.
// ...
let mut balance_data = user_balance_account.data.borrow_mut();
let mut balance = u64::from_le_bytes(balance_data[0..8].try_into().unwrap());
// The user is allowed to withdraw if they are the designated authority
if user_balance_account.owner == &program_id {
if authority_account.key == &some_expected_authority_pubkey {
balance = balance - withdraw_amount; // Potential underflow
balance_data[0..8].copy_from_slice(&balance.to_le_bytes());
}
}
// ...
Based on what you've learned, identify at least two distinct security vulnerabilities in this code snippet and explain how you would fix them.
Show answer
This code has two major vulnerabilities:
-
Missing Signer Check: The code checks if the
authority_account.keymatches an expected public key (if authority_account.key == &some_expected_authority_pubkey). However, it never checks ifauthority_accounthas actually signed the transaction (authority_account.is_signer). An attacker could simply pass in the correct public key for the authority account without having the private key, and this check would pass.- Fix: Add the check
if !authority_account.is_signer { return Err(...) }before the withdrawal logic.
- Fix: Add the check
-
Integer Underflow: The line
balance = balance - withdraw_amount;performs raw subtraction. If the user'sbalanceis less than thewithdraw_amount, this operation will underflow, wrapping the balance around to a very large number. The attacker could start with 10 tokens, "withdraw" 100, and end up with a near-maximumu64balance.- Fix: Use checked arithmetic:
balance = balance.checked_sub(withdraw_amount).ok_or(ProgramError::InsufficientFunds)?;. This will safely return an error if an underflow would occur.
- Fix: Use checked arithmetic:
(A less obvious but still valid point is that the ownership check if user_balance_account.owner == &program_id is good, but the code doesn't check for account confusion/type cosplay. It assumes user_balance_account is the correct type of account. In Anchor, this would be handled by the discriminator).
Conclusion
Congratulations on completing your first dive into Solana program security! Adopting a security-first mindset is the most important trait of a successful smart contract developer.
Key Takeaways:
- Solana is Attacker-Controlled: Never trust any data or account passed to your program. Validate everything.
- Verify Authority: Always check that privileged accounts are signers (
is_signer) and that state accounts are owned by your program (owner). - Prevent Account Confusion: Ensure an account is of the expected type. Anchor's discriminators handle this automatically when using its account wrappers.
- Guard Against Overflows: Standard Rust release builds do not protect against integer overflows. You must explicitly enable overflow checks in
Cargo.tomlor usechecked_*arithmetic functions.
Preview of the Next Lesson
Today, we learned how to manually review a program for common vulnerabilities. In our next lesson, we will focus on proactive prevention. We will explore how Anchor's extensive system of account constraints—like signer, mut, has_one, and more—allows you to declare your security requirements directly in your Accounts struct, turning potential runtime exploits into compile-time or entry-time errors.
Can't find a good explanation? Sign up and we'll make it for you
Sign up