Rust Solana
Security scanning for Solana programs written in Rust. Solana smart contracts (programs) are written in Rust, offering memory safety and performance....
Last updated: January 14, 2026
Rust/Solana Guide
Security scanning for Solana programs written in Rust.
Overview
Solana smart contracts (programs) are written in Rust, offering memory safety and performance. BlockSecOps provides specialized scanners for Solana-specific security issues.
Available Scanners
| Scanner | Focus | Description |
|---|---|---|
| Cargo Audit | Dependencies | Known vulnerabilities in crates |
| Clippy | Code quality | Rust linting and best practices |
| Soteria | Solana security | Solana-specific vulnerability detection |
| X-ray | Deep analysis | Advanced security scanning |
Solana Security Focus
Account Validation
Solana programs must validate all accounts:
// BAD: No account validation
pub fn process_transfer(accounts: &[AccountInfo]) -> ProgramResult {
let source = &accounts[0];
let destination = &accounts[1];
// Transfer without validation...
}
// GOOD: Proper account validation
pub fn process_transfer(accounts: &[AccountInfo]) -> ProgramResult {
let source = &accounts[0];
let destination = &accounts[1];
// Validate account ownership
if source.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
// Validate signer
if !source.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// Now safe to proceed...
}
Signer Verification
Always verify required signers:
// Verify signer
if !authority.is_signer {
msg!("Authority must sign");
return Err(ProgramError::MissingRequiredSignature);
}
PDA Verification
Validate Program Derived Addresses:
// Verify PDA
let (expected_pda, bump) = Pubkey::find_program_address(
&[b"vault", user.key.as_ref()],
program_id
);
if vault.key != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
Common Vulnerabilities
Missing Owner Check
// BAD: Attacker can pass any account
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let vault = &accounts[0];
// No owner verification...
}
// GOOD: Verify program owns the account
pub fn withdraw(accounts: &[AccountInfo], amount: u64) -> ProgramResult {
let vault = &accounts[0];
if vault.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
// Safe to proceed...
}
Missing Signer Check
// BAD: Anyone can call this
pub fn admin_function(accounts: &[AccountInfo]) -> ProgramResult {
let admin = &accounts[0];
// No signer check...
}
// GOOD: Require signature
pub fn admin_function(accounts: &[AccountInfo]) -> ProgramResult {
let admin = &accounts[0];
if !admin.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
}
Integer Overflow
// BAD: Can overflow
let total = amount1 + amount2;
// GOOD: Checked arithmetic
let total = amount1.checked_add(amount2)
.ok_or(ProgramError::ArithmeticOverflow)?;
Type Confusion
// BAD: No type validation
let account_data = Account::unpack(&account.data.borrow())?;
// GOOD: Validate discriminator/type
let account_data = Account::unpack(&account.data.borrow())?;
if account_data.account_type != AccountType::Vault {
return Err(ProgramError::InvalidAccountData);
}
Anchor Framework
If using Anchor, additional validation is automatic:
use anchor_lang::prelude::*;
#[program]
pub mod my_program {
use super::*;
pub fn transfer(ctx: Context<Transfer>, amount: u64) -> Result<()> {
// Anchor validates:
// - Account ownership
// - Signers (from #[account] attributes)
// - Account types
// Your logic here
Ok(())
}
}
#[derive(Accounts)]
pub struct Transfer<'info> {
#[account(mut, has_one = authority)]
pub vault: Account<'info, Vault>,
pub authority: Signer<'info>, // Automatically verified
#[account(mut)]
pub destination: AccountInfo<'info>,
}
Project Setup
Directory Structure
program/
├── src/
│ ├── lib.rs
│ ├── processor.rs
│ ├── instruction.rs
│ ├── state.rs
│ └── error.rs
├── Cargo.toml
└── Anchor.toml # If using Anchor
Cargo.toml
[package]
name = "my-program"
version = "0.1.0"
edition = "2021"
[dependencies]
solana-program = "1.16"
borsh = "0.10"
thiserror = "1.0"
[dev-dependencies]
solana-program-test = "1.16"
solana-sdk = "1.16"
What to Upload
Include:
src/directory with all.rsfilesCargo.tomlandCargo.lockAnchor.tomlif applicable
Exclude:
target/build directory- Test artifacts
Scanner Detection
Cargo Audit
Checks for known vulnerabilities in dependencies:
Crate: borsh
Version: 0.9.0
Warning: RUSTSEC-2023-0001
Title: Memory corruption vulnerability
Clippy
Finds code quality and potential bugs:
warning: this could be a `const fn`
--> src/processor.rs:45:1
|
45 | pub fn get_fee() -> u64 {
| ^^^^^^^^^^^^^^^^^^^^^^^^
Soteria
Finds Solana-specific issues:
src/processor.rs:78 - Missing signer check
The authority account is not verified as a signer
Severity: Critical
src/processor.rs:92 - Missing owner check
Account ownership not validated before deserialization
Severity: High
X-ray
Deep security analysis:
src/processor.rs:105 - Integer overflow risk
Unchecked arithmetic operation could overflow
Severity: High
Recommendation: Use checked_add/checked_sub
Best Practices
Account Validation Checklist
fn validate_accounts(accounts: &[AccountInfo], program_id: &Pubkey) -> ProgramResult {
let account = &accounts[0];
// 1. Check owner
if account.owner != program_id {
return Err(ProgramError::IncorrectProgramId);
}
// 2. Check signer (if required)
if !account.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
// 3. Check writable (if modifying)
if !account.is_writable {
return Err(ProgramError::InvalidAccountData);
}
// 4. Check data size
if account.data_len() < EXPECTED_SIZE {
return Err(ProgramError::InvalidAccountData);
}
// 5. Validate PDA if applicable
let (expected_pda, _) = Pubkey::find_program_address(
&[SEED],
program_id
);
if account.key != &expected_pda {
return Err(ProgramError::InvalidSeeds);
}
Ok(())
}
Safe Arithmetic
// Always use checked arithmetic
let result = a.checked_add(b)
.ok_or(ProgramError::ArithmeticOverflow)?;
let result = a.checked_sub(b)
.ok_or(ProgramError::InsufficientFunds)?;
let result = a.checked_mul(b)
.ok_or(ProgramError::ArithmeticOverflow)?;
let result = a.checked_div(b)
.ok_or(ProgramError::DivisionByZero)?;
Error Handling
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ProgramError {
#[error("Invalid instruction")]
InvalidInstruction,
#[error("Missing required signature")]
MissingSignature,
#[error("Invalid account owner")]
InvalidOwner,
#[error("Arithmetic overflow")]
Overflow,
}
Cross-Program Invocation (CPI) Security
// Validate CPI calls
pub fn safe_cpi(
program_id: &Pubkey,
accounts: &[AccountInfo],
) -> ProgramResult {
// Verify the program being called
let target_program = &accounts[0];
// Whitelist known programs
let allowed_programs = [
spl_token::id(),
system_program::id(),
];
if !allowed_programs.contains(target_program.key) {
return Err(ProgramError::InvalidProgramId);
}
// Proceed with CPI...
Ok(())
}
Next Steps
- Language Guides Overview - Other languages
- Security Best Practices - Secure coding
- Scanner Catalog - All scanners