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. - Auditing Solana programs (native Rust or Anchor) - Reviewing cross-program invocation (CPI) logic
.rs// Native Solana program indicators use solana_program::{ account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey, program::invoke, program::invoke_signed, }; entrypoint!(process_instruction); // Anchor framework indicators use 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 patterns AccountInfo, Pubkey invoke(), invoke_signed() Signer<'info>, Account<'info> #[account(...)] with constraints seeds, bump
programs/*/src/lib.rs - Program implementationAnchor.toml - Anchor configurationCargo.toml with solana-program or anchor-langtests/ - Program testsprograms/*/src/lib.rs)# Find all CPI calls rg "invoke\(|invoke_signed\(" programs/ # Check for program ID validation before each # Should see program ID checks immediately before invoke
Program<'info, T> type# Find PDA usage rg "find_program_address|create_program_address" programs/ rg "seeds.*bump" programs/ # Anchor: Check for seeds constraints rg "#\[account.*seeds" programs/
find_program_address() or Anchor seeds constraint# Find account deserialization rg "try_from_slice|try_deserialize" programs/ # Should see owner checks before deserialization rg "\.owner\s*==|\.owner\s*!=" programs/
Account<'info, T> and Signer<'info># Find instruction introspection usage rg "load_instruction_at|load_current_index|get_instruction_relative" programs/ # Check for checked versions rg "load_instruction_at_checked|load_current_index_checked" programs/
# 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)
## [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 145 pub 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(()) }
Program<'info, Token> type:use 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(()) }
unchecked-cpi-program-id--- ## 7. 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) --- ## 8. 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)` 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` # Run local validator for testing solana-test-validator # Deploy and test program anchor test
building-secure-contracts/not-so-smart-contracts/solana/invoke()Program<'info, T> typefind_program_address() or Anchor seeds constraintaccount.owner == expected_program_idAccount<'info, T> typeis_signeraccount.is_signer == trueSigner<'info> typeload_instruction_at_checked()