Solana Vulnerability Scanner

作者 trailofbits82fe82262526无许可证7.4K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库昨天更新

Scans Solana programs for 6 critical vulnerabilities including arbitrary CPI, improper PDA validation, missing signer/ownership checks, and sysvar spoofing. Use when auditing Solana/Anchor programs.

仅含说明Security
AI 生成的概览

扫描 Solana 与 Anchor 程序中的六类关键安全漏洞模式,并给出发现与修复建议。

功能
指导对 Solana 程序(原生 Rust 或 Anchor)进行审计,覆盖六类平台特有漏洞模式:任意 CPI、PDA 校验不当、缺少所有权检查、缺少签名者检查、sysvar 账户伪造以及指令内省不当。它给出平台识别、CPI、PDA、账户校验与内省审查的步骤,并产出一张对每种模式都给出结论的覆盖表,以及包含代码示例与缓解措施的详细发现。此外还建议单元测试与集成测试,并提供审计前检查清单。
适用场景
适用于审计或审查 Solana 程序,尤其是上线前的安全检查,以及核查跨程序调用逻辑、PDA 实现、账户校验或指令内省时。原生 Rust 与 Anchor 代码库均适用。
运行要求
仅为说明性指令,不附带脚本。需要能够访问程序源码,并参考随附的漏洞模式文档。文中提到可选工具,如 ripgrep、Cargo、Anchor、Solana 测试验证器以及 Trail of Bits Solana lints。

Solana Vulnerability Scanner

1. Purpose

Systematically scan Solana programs (native and Anchor framework) for platform-specific security vulnerabilities related to cross-program invocations, account validation, and program-derived addresses. This skill encodes 6 critical vulnerability patterns unique to Solana's account model.

2. When to Use This Skill

  • Auditing Solana programs (native Rust or Anchor)
  • Reviewing cross-program invocation (CPI) logic
  • Validating program-derived address (PDA) implementations
  • Pre-launch security assessment of Solana protocols
  • Reviewing account validation patterns
  • Assessing instruction introspection logic

3. Platform Detection

File Extensions & Indicators

  • Rust files: .rs

Language/Framework Markers

rust
// Native Solana program indicatorsuse solana_program::{    account_info::AccountInfo,    entrypoint,    entrypoint::ProgramResult,    pubkey::Pubkey,    program::invoke,    program::invoke_signed,};
entrypoint!(process_instruction);
// Anchor framework indicatorsuse anchor_lang::prelude::*;
#[program]pub mod my_program {    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {        // Program logic    }}
#[derive(Accounts)]pub struct Initialize<'info> {    #[account(mut)]    pub authority: Signer<'info>,}
// Common patternsAccountInfo, Pubkeyinvoke(), invoke_signed()Signer<'info>, Account<'info>#[account(...)] with constraintsseeds, bump

Project Structure

  • programs/*/src/lib.rs - Program implementation
  • Anchor.toml - Anchor configuration
  • Cargo.toml with solana-program or anchor-lang
  • tests/ - Program tests

Tool Support

  • Trail of Bits Solana Lints: Rust linters for Solana
  • Installation: Add to Cargo.toml
  • anchor test: Built-in testing framework
  • Solana Test Validator: Local testing environment

4. How This Skill Works

When invoked, I will:

  1. Search your codebase for Solana/Anchor programs
  2. Analyze each program for the 6 vulnerability patterns
  3. Report findings with file references and severity, above them a coverage table carrying a verdict for every pattern
  4. Provide fixes for each identified issue
  5. Check account validation and CPI security

5. Example Output


6. Vulnerability Patterns (6 Patterns)

I check for 6 critical vulnerability patterns unique to Solana. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md [blocked].

Pattern Summary:

  1. Arbitrary CPI ⚠️ CRITICAL - User-controlled program IDs in CPI calls
  2. Improper PDA Validation ⚠️ CRITICAL - Using create_program_address without canonical bump
  3. Missing Ownership Check ⚠️ HIGH - Deserializing accounts without owner validation
  4. Missing Signer Check ⚠️ CRITICAL - Authority operations without is_signer check
  5. Sysvar Account Check ⚠️ HIGH - Spoofed sysvar accounts (pre-Solana 1.8.1)
  6. Improper Instruction Introspection ⚠️ MEDIUM - Absolute indexes allowing reuse

For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md [blocked].

7. Scanning Workflow

Step 1: Platform Identification

  1. Verify Solana program (native or Anchor)
  2. Check Solana version (1.8.1+ for sysvar security)
  3. Locate program source (programs/*/src/lib.rs)
  4. Identify framework (native vs Anchor)

Step 2: CPI Security Review

bash
# Find all CPI callsrg "invoke\(|invoke_signed\(" programs/
# Check for program ID validation before each# Should see program ID checks immediately before invoke

For each CPI:

  • Program ID validated before invocation
  • Cannot pass user-controlled program accounts
  • Anchor: Uses Program<'info, T> type

Step 3: PDA Validation Check

bash
# Find PDA usagerg "find_program_address|create_program_address" programs/rg "seeds.*bump" programs/
# Anchor: Check for seeds constraintsrg "#\[account.*seeds" programs/

For each PDA:

  • Uses find_program_address() or Anchor seeds constraint
  • Bump seed stored and reused
  • Not using user-provided bump

Step 4: Account Validation Sweep

bash
# Find account deserializationrg "try_from_slice|try_deserialize" programs/
# Should see owner checks before deserializationrg "\.owner\s*==|\.owner\s*!=" programs/

For each account used:

  • Owner validated before deserialization
  • Signer check for authority accounts
  • Anchor: Uses Account<'info, T> and Signer<'info>

Step 5: Instruction Introspection Review

bash
# Find instruction introspection usagerg "load_instruction_at|load_current_index|get_instruction_relative" programs/
# Check for checked versionsrg "load_instruction_at_checked|load_current_index_checked" programs/
  • Using checked functions (Solana 1.8.1+)
  • Using relative indexing
  • Proper correlation validation

Step 6: Trail of Bits Solana Lints

toml
# Add to Cargo.toml[dependencies]solana-program = "1.17"  # Use latest version
[lints.clippy]# Enable Solana-specific lints# (Trail of Bits solana-lints if available)

8. Reporting Format

Coverage Table

Report on every pattern in §6, whether or not it turned anything up. Emit this table above the findings, with all 6 rows present:

#PatternVerdictEvidence
1Arbitrary CPIn/athis program makes no cross-program invocations
2Improper PDA Validation
3Missing Ownership Check
4Missing Signer Check
5Sysvar Account Check
6Improper Instruction Introspection

Each verdict is one of:

  • found — cite file:line and write the finding up in full below.
  • clear — the pattern applies to this program and the program handles it. Name the constraint, account type, or check you searched for, so a reader can repeat the search.
  • n/a — the pattern cannot apply here. Give the reason in one clause ("this program makes no CPI calls"). Not having looked is not n/a. Pattern 5 is version-scoped (pre-Solana 1.8.1): cite the solana-program version the program targets rather than dropping the row, since "targets 1.17, fixed upstream" and "did not look" are otherwise the same answer.

A table with fewer than 6 rows is an incomplete scan and must be reported as one. A row whose Verdict cell is empty is incomplete in the same way: row 1 above is filled in to show the shape, and every row is filled in the same way before the report is done. Six clear verdicts is a result a reader can act on. A report that covers two patterns and says nothing about the other four reads exactly like a clean program, and that is the failure this table exists to prevent.

Finding Template

markdown
## [CRITICAL] Arbitrary CPI - Unchecked Program ID
**Location**: `programs/vault/src/lib.rs:145-160` (withdraw function)
**Description**:The `withdraw` function performs a CPI to transfer SPL tokens without validating that the provided `token_program` account is actually the SPL Token program. An attacker can provide a malicious program that appears to perform a transfer but actually steals tokens or performs unauthorized actions.
**Vulnerable Code**:```rust// lib.rs, line 145pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {    let token_program = &ctx.accounts.token_program;
    // WRONG: No validation of token_program.key()!    invoke(        &spl_token::instruction::transfer(...),        &[            ctx.accounts.vault.to_account_info(),            ctx.accounts.destination.to_account_info(),            ctx.accounts.authority.to_account_info(),            token_program.to_account_info(),  // UNVALIDATED        ],    )?;    Ok(())}```
**Attack Scenario**:1. Attacker deploys malicious "token program" that logs transfer instruction but doesn't execute it2. Attacker calls withdraw() providing malicious program as token_program3. Vault's authority signs the transaction4. Malicious program receives CPI with vault's signature5. Malicious program can now impersonate vault and drain real tokens
**Recommendation**:Use Anchor's `Program<'info, Token>` type:```rustuse anchor_spl::token::{Token, Transfer};
#[derive(Accounts)]pub struct Withdraw<'info> {    #[account(mut)]    pub vault: Account<'info, TokenAccount>,    #[account(mut)]    pub destination: Account<'info, TokenAccount>,    pub authority: Signer<'info>,    pub token_program: Program<'info, Token>,  // Validates program ID automatically}
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {    let cpi_accounts = Transfer {        from: ctx.accounts.vault.to_account_info(),        to: ctx.accounts.destination.to_account_info(),        authority: ctx.accounts.authority.to_account_info(),    };
    let cpi_ctx = CpiContext::new(        ctx.accounts.token_program.to_account_info(),        cpi_accounts,    );
    anchor_spl::token::transfer(cpi_ctx, amount)?;    Ok(())}```
**References**:- building-secure-contracts/not-so-smart-contracts/solana/arbitrary_cpi- Trail of Bits lint: `unchecked-cpi-program-id`

9. Priority Guidelines

Critical (Immediate Fix Required)

  • Arbitrary CPI (attacker-controlled program execution)
  • Improper PDA validation (account spoofing)
  • Missing signer check (unauthorized access)

High (Fix Before Launch)

  • Missing ownership check (fake account data)
  • Sysvar account check (authentication bypass, pre-1.8.1)

Medium (Address in Audit)

  • Improper instruction introspection (logic bypass)

10. Testing Recommendations

Unit Tests

rust
#[cfg(test)]mod tests {    use super::*;
    #[test]    #[should_panic]    fn test_rejects_wrong_program_id() {        // Provide wrong program ID, should fail    }
    #[test]    #[should_panic]    fn test_rejects_non_canonical_pda() {        // Provide non-canonical bump, should fail    }
    #[test]    #[should_panic]    fn test_requires_signer() {        // Call without signature, should fail    }}

Integration Tests (Anchor)

typescript
import * as anchor from "@coral-xyz/anchor";
describe("security tests", () => {  it("rejects arbitrary CPI", async () => {    const fakeTokenProgram = anchor.web3.Keypair.generate();
    try {      await program.methods        .withdraw(amount)        .accounts({          tokenProgram: fakeTokenProgram.publicKey, // Wrong program        })        .rpc();
      assert.fail("Should have rejected fake program");    } catch (err) {      // Expected to fail    }  });});

Solana Test Validator

bash
# Run local validator for testingsolana-test-validator
# Deploy and test programanchor test

11. Additional Resources


12. Quick Reference Checklist

Before completing Solana program audit:

CPI Security (CRITICAL):

  • ALL CPI calls validate program ID before invoke()
  • Cannot use user-provided program accounts
  • Anchor: Uses Program<'info, T> type

PDA Security (CRITICAL):

  • PDAs use find_program_address() or Anchor seeds constraint
  • Bump seed stored and reused (not user-provided)
  • PDA accounts validated against canonical address

Account Validation (HIGH):

  • ALL accounts check owner before deserialization
  • Native: Validates account.owner == expected_program_id
  • Anchor: Uses Account<'info, T> type

Signer Validation (CRITICAL):

  • ALL authority accounts check is_signer
  • Native: Validates account.is_signer == true
  • Anchor: Uses Signer<'info> type

Sysvar Security (HIGH):

  • Using Solana 1.8.1+
  • Using checked functions: load_instruction_at_checked()
  • Sysvar addresses validated

Instruction Introspection (MEDIUM):

  • Using relative indexes for correlation
  • Proper validation between related instructions
  • Cannot reuse same instruction across multiple calls

Testing:

  • Unit tests cover all account validation
  • Integration tests with malicious inputs
  • Local validator testing completed
  • Trail of Bits lints enabled and passing
  • Coverage table emitted with all 6 rows, each carrying a verdict of found, clear or n/a with a reason

13. Rationalizations to Reject

  • "The program is small, so most patterns obviously don't apply." Obvious to whom? An n/a costs one clause and makes the judgment reviewable. Silence records nothing, and a reader cannot tell it apart from not having checked.
  • "The Trail of Bits lints pass, so the program is clean." The lints cover a subset of these 6 patterns. A clean lint run is one row of evidence, not a verdict on the patterns it never examined. Say which patterns it covered.
  • "I checked the patterns that matter for this program." Deciding which patterns matter is the scan, not a precondition for starting it. Rank by severity after the table is complete, not by leaving rows out.
  • "No findings, so there is nothing to report." A zero-finding scan still emits the full coverage table. That table is the deliverable: it is what distinguishes a program that was examined from one that was glanced at.
  • "Anchor handles account validation." Name the constraint. Anchor validates what the account struct declares and nothing more: #[account(mut)] is not an ownership check, and UncheckedAccount opts out entirely. Cite the attribute, not the framework.
  • "The PDA derivation makes it safe." Derivation is not validation. create_program_address without the canonical bump admits multiple valid addresses, which is pattern 2 in full.

来源与署名

来源:trailofbits/skills位于plugins/building-secure-contracts/skills/solana-vulnerability-scanner提交82fe822

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架