Pontos-Chave de Segurança de Programa
Segurança de programa Solana é segurança de conta. Chamadores escolhem quais contas e programas entram em uma instrução; Sealevel aplica locks, regras de escrita de propriedade e orçamentos de computação, não seu saldo de cofre, lista de admin ou vínculo de mint.
Esta página é o mapa conceitual para Segurança e Auditoria de Programa - superfície de ataque Sealevel, validação de conta, verificações de signatário e owner, ataques PDA e semente, reinicialização, aritmética, risco CPI e reentrância, e um fluxo de auditoria prático no Agave 4.1.1.
Resumo
- Toda instrução é uma interface adversarial: prove quem assinou, quem possui cada conta, qual PDA e tipo você toca, depois mute estado com matemática verificada e ordem CPI segura.
- Por que Importa: Exploits raramente precisam de quebra cripto nova. Substituem cofre errado, pulam signatário, reinicializam admin, fazem wrap de saldo ou fazem CPI para programa malicioso que retorna "success."
- Conceitos Chave: validação de conta, verificações de signatário / owner, sementes PDA e bump, guardas de reinicialização, aritmética verificada, listas de permissões CPI, checks-effects-interactions (CEI), design fail-closed, fluxo de auditoria.
- Quando Usar Este Modelo: Projetar novas instruções, revisar
#[derive(Accounts)], prep para auditoria externa, triagem de achados ou ensinar o modelo de ameaça Solana. - Limitações / Trade-offs: Constraints Anchor capturam muitas classes mas não regras de negócio;
UncheckedAccounteremaining_accountsreintroduzem risco; revisões de segurança não substituem fuzzing ou revisão de design econômico. - Tópicos Relacionados: Ataques de Validação de Conta, Verificações de Signatário e Owner, Ataques de PDA e Semente, Ataques de Reinicialização, Aritmética e Overflow, Riscos de CPI e Reentrância, Fluxo de Auditoria.
Fundamentos
Sealevel executa programas como executáveis sem estado. Estado durável vive em contas que a transação lista antecipadamente. Clientes (ou atacantes) fornecem essas contas, flags de signatário e flags writable. O runtime verifica que signatários declarados realmente assinaram e que somente o programa proprietário pode escrever dados da conta. Ele não sabe que "esta TokenAccount deve corresponder a vault.mint" ou "somente authority pode sacar."
Essa lacuna é a superfície de ataque:
Client / attacker supplies:
program_id | instruction data | AccountMeta[] (keys, is_signer, is_writable)
Runtime guarantees:
signatures match is_signer | locks from is_writable | owner write rules | CU budget
Your program must guarantee:
right identity signed | right owners and types | right PDAs | right amounts | safe CPIsFail closed é a postura padrão: se validação incompleta, rejeite com erro tipado. Nunca assuma "clientes honestos" ou "nosso frontend só constrói txs boas." Frontends e indexadores não fazem parte da fronteira de confiança; verificações on-chain sim.
Anchor 0.32.1 codifica verificações comuns (Signer, Account<T>, seeds, has_one, Program<T>). Isso não é revisão completa. Lacunas aparecem com AccountInfo, UncheckedAccount, remaining_accounts, init_if_needed sem guarda e regras de negócio que constraints não expressam.
Modelo de ameaça para um programa típico de cofre ou mercado:
| Ativo em risco | Falha típica | Controle de primeira linha |
|---|---|---|
| Tokens / lamports | Signatário ausente ou conta de token errada | Signer, constraints mint/owner |
| Autoridade admin / config | Reinit ou has_one ausente | Init único, authority armazenada + signatário |
| Contabilidade de protocolo | Overflow, arredondamento ruim | checked_*, intermediários u128 |
| Composabilidade | CPI para programa impostor | Lista de permissões de ID de programa, CEI |
| Identidade de estado | PDA errada ou confusão de tipo | seeds + bump, discriminadores |
Mecânica e Interações
Validação de conta e confusão de tipo
Atacantes passam contas que parecem utilizáveis: mesmo tamanho, de propriedade do seu programa ou deserializáveis no tipo errado. Validação significa mais que "deserializa."
Checklist por papel de conta:
- Owner - programa esperado (
Account<T>para seus tipos;require_keys_eq!para SPL Token, Sysvar, etc.). - Tipo / discriminador - disc de conta Anchor ou constante nativa para Vault nunca ser User.
- Constraints relacionais - mint corresponde ao cofre, owner da conta de token é a PDA do cofre, config corresponde ao mercado.
- Mutabilidade - somente contas que você pretende mudar são
mut. - PDA canônica -
seeds+bumparmazenado para endereço derivado, não escolhido livremente.
#[derive(Accounts)]
pub struct Deposit<'info> {
#[account(mut, has_one = authority, has_one = mint)]
pub vault: Account<'info, Vault>,
#[account(
mut,
constraint = user_ata.mint == vault.mint @ ErrorCode::MintMismatch,
constraint = user_ata.owner == user.key() @ ErrorCode::WrongAtaOwner,
)]
pub user_ata: Account<'info, TokenAccount>,
pub authority: Signer<'info>,
pub user: Signer<'info>,
pub token_program: Program<'info, Token>,
}UncheckedAccount e AccountInfo bruto pulam verificações de owner e disc até você adicioná-las. Trate todo campo assim como achado até provar validado.
Verificações de signatário e owner
Signatário: qualquer caminho que move fundos, muda autoridade, fecha contas ou config sensível a upgrade precisa de assinatura criptográfica da chave certa (ou PDA assinada via invoke_signed com sementes controladas pelo programa).
Owner: somente o programa proprietário pode reescrever dados. Ler conta que seu programa não possui sem verificar owner é como confusão de tipo e mints falsos entram. Para contas de token, owner deve ser o programa Token ou Token-2022 que você pretende; para seu estado, owner deve ser seu program_id.
Padrão crítico comum: cofre armazena authority: Pubkey mas a instrução nunca exige authority como Signer ou nunca vincula com has_one.
Ataques PDA e semente
PDAs são autoridade e identidade de endereço. Sementes fracas ou ambíguas permitem que atacante apresente PDA diferente que seu programa ainda "aceita."
Regras práticas:
- Inclua todas as dimensões de identidade nas sementes (usuário, mint, id de mercado, prefixo de papel).
- Armazene o bump canônico no init; reutilize
bump = account.bumpdepois (sem busca ambígua de bump em hot paths). - Assine CPIs somente com as mesmas sementes usadas para criar a PDA.
- Não deixe usuários fornecerem bytes de semente arbitrários sem separação de domínio (
b"vault", tags de version).
#[account(
seeds = [b"vault", authority.key().as_ref(), mint.key().as_ref()],
bump = vault.bump,
)]
pub vault: Account<'info, Vault>,Reinicialização
Init escreve discriminadores, autoridades e saldos. Reexecutar init (ou init_if_needed sem flag) pode resetar admin para o atacante ou reviver forma de conta fechada.
Prefira init único para estado permanente. Se precisar init_if_needed, exija !is_initialized (ou equivalente) antes de escrever campos de autoridade, e nunca reabra contas fechadas sem caminho deliberado e auditado. No close, zere dados / disc e transfira lamports para que a conta não possa ser tratada como estado ativo.
Aritmética e overflow
Builds release BPF fazem wrap em +, -, * simples. Matemática de cofre, cunhagem de shares, acúmulo de taxa e fórmulas AMM precisam checked_* (ou ops saturating onde intencional) e frequentemente intermediários u128 antes de cast de volta para u64.
Arredondamento deve favorecer o protocolo em mints e resgates voltados ao usuário para que dust não possa ser farmado em valor gratuito. Divisão por zero e casos extremos de pool vazio são bugs de segurança, não mera UX.
Risco CPI e estilo reentrância
Invocação entre programas estende confiança a outro programa. Riscos:
- ID de programa errado - "programa token" impostor noop ou rouba.
- Remaining accounts não validadas - contas de callback controladas por atacante.
- Estado após CPI - callee reentra seu programa (ou par) enquanto saldos ainda parecem pré-atualização.
Mitigações: liste permissões de callees com Program<'info, T> ou require_keys_eq!; atualize estado do programa antes de CPI externo (CEI); evite callbacks não confiáveis a menos que projete verificações de dívida estilo flash-loan explícitas; passe somente contas que o callee precisa.
vault.balance = vault.balance.checked_sub(amount).ok_or(ErrorCode::Overflow)?;
// effects first, then interaction
token::transfer(cpi_ctx, amount)?;Solana não é idêntica à reentrância EVM, mas ordem de chamada ruim e alvos CPI não confiáveis ainda perdem fundos.
Fluxo de auditoria (espinha dorsal)
Trabalho de segurança é processo tanto quanto código:
- Congele tag RC, trave dependências (Anchor 0.32.1, pins Agave/CLI), documente modelo de ameaça.
- Mapeie internamente cada instrução para classes do Catálogo de Ataques Sealevel (signatário, owner, substituição, PDA, reinit, matemática, CPI, close, oráculo).
- Testes negativos no LiteSVM / testes Anchor: signatário errado, mint errado, programa errado, double init, quantidades overflow.
- Fuzz parsing e caminhos pesados em matemática quando complexidade justificar.
- Build verificável para auditores corresponderem fonte ao hash on-chain.
- Auditoria externa + ciclo de correções; re-diff após toda mudança em
Accountsou aritmética. - Bug bounty e plano de upgrade/rollback para risco residual.
Considerações Avançadas e Aplicações
| Camada de controle | O que captura | O que perde | Quando reforçar |
|---|---|---|---|
| Tipos de conta Anchor | Owner + disc para Account<T>, muitas constraints PDA | Regras de negócio, caminhos Unchecked | Padrão para todas as contas de estado |
require_* manual | Invariantes relacionais e econômicos | Fácil omitir em novas ixs | Toda revisão de handler novo |
| CEI + lista de permissões de programa | Roubo CPI clássico / programas impostores | Bugs de design econômico | Qualquer movimento de token ou callback |
| Matemática verificada + testes de propriedade | Bordas overflow e matemática de share | Jogos de oráculo e MEV | Cofres, AMMs, empréstimos |
| Auditoria externa + bounty | Olhos frescos, classes conhecidas do catálogo | Ataques de orçamento infinito, upgrades futuros | Pré-mainnet e upgrades maiores |
Oráculos são entradas não confiáveis: verifique owner do feed, obsolescência e confiança; fail closed em dados ruins.
Extensões Token-2022 (hooks, delegates permanentes) mudam quem pode mover tokens. Fixe ID do programa token; não assuma semântica clássica SPL Token.
Close e rent: exija signatário e destino certos; zere dados para contas não reviverem sob verificações fracas. Prefira close = do Anchor com constraints.
Autoridade de upgrade é root. Multisig ou governança, builds verificáveis e imutabilidade opcional após maturidade são segurança operacional.
Clientes (@solana/kit 7.0.0, carteiras) ajudam usuários honestos somente. Programas defendem contra todos os outros.
No Agave 4.1.1 (CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1), exercite conjuntos adversariais de contas no LiteSVM ou validadores locais. Trate toda instrução nova como nova superfície de ataque.
Equívocos Comuns
- "O runtime valida minhas regras de cofre." Valida assinaturas, locks, owners para escritas e CU. Regras de protocolo são só suas.
- "Anchor significa que estamos seguros." Anchor codifica verificações comuns; contas Unchecked, constraints ruins e bugs de matemática ainda vão para produção.
- "Se deserializa, a conta está ok." Deserialização não é vínculo de mint, autoridade ou identidade PDA.
- "PDAs não podem ser forjadas." Sementes erradas ou incompletas produzem PDA válida diferente sob seu programa. Sementes definem a política de segurança.
- "init_if_needed é sempre seguro." Sem flag initialized (ou equivalente), reinit redefine campos críticos.
- "Rust previne overflow on-chain." Wrap release BPF é real; use matemática verificada para caminhos de valor.
- "CPI para Token é sempre seguro." Somente se ID de programa for o programa Token/Token-2022 real e contas corresponderem a constraints de mint e autoridade.
- "Vamos corrigir segurança na auditoria." Auditorias são amostragem. Projete fail-closed primeiro; auditorias capturam bugs residuais.
- "Contas readonly não podem nos prejudicar." Configs, oráculos e mints falsos são frequentemente readonly mas controlam totalmente resultados econômicos.
- "Fechar conta encerra todo risco." Close incompleto ou destino errado cria roubo de rent e problemas de revival.
FAQs
Qual é a superfície de ataque Sealevel para um programa?
Tudo que o chamador pode escolher: quais contas e programas aparecem, quem assina, dados de instrução e alvos CPI. O runtime não aplica seus invariantes de mint, autoridade ou saldo. Veja Catálogo de Ataques Sealevel.
Por que verificações de signatário ausentes são tão comuns e severas?
Sem Signer obrigatório (ou caminho PDA invoke_signed), qualquer chave de conta pode ser nomeada como "authority" enquanto somente o pagador de taxa realmente assina. Isso habilita saques não autorizados e tomada de admin. Veja Verificações de Signatário e Owner.
O que é substituição de conta?
Passar cofre, conta de token, mint ou config diferente do que o protocolo pretende, frequentemente ainda de propriedade de programa plausível. Corrija com constraints relacionais (has_one, igualdade de mint, seeds). Veja Ataques de Validação de Conta.
Como funcionam ataques de semente PDA?
Se sementes omitem campos de identidade ou bumps não são canônicos, atacantes derivam PDAs alternativas que sua instrução ainda aceita, impersonando cofres ou escrows. Armazene bumps e fixe listas completas de sementes. Veja Ataques de PDA e Semente.
Quando reinicialização é possível?
Quando init pode rodar novamente em estado ativo: init_if_needed sem guardas, discriminadores ausentes ou contas fechadas recriadas sob verificações fracas. Veja Ataques de Reinicialização.
Solana precisa de aritmética verificada?
Sim para caminhos de valor. Aritmética simples pode fazer wrap on-chain; fórmulas de share e swap devem usar checked_* e arredondamento cuidadoso. Veja Aritmética e Overflow.
O que é CEI em contexto CPI Solana?
Checks-effects-interactions: valide, atualize estado de propriedade do programa, depois chame para fora. Limita leituras reentrantes de saldos obsoletos durante CPIs não confiáveis ou recursivos. Veja Riscos de CPI e Reentrância.
Como valido alvo CPI de programa?
Use Program<'info, Token> (ou seu tipo conhecido) ou require_keys_eq! contra ID constante. Nunca CPI para chave de programa fornecida pelo usuário sem lista de permissões explícita.
Token e Token-2022 são intercambiáveis para segurança?
Não. IDs de programa e comportamentos de extensão diferentes (hooks, taxas de transferência, delegates) mudam quem pode mover fundos. Fixe o programa e teste mints com extensões habilitadas.
O que uma pré-auditoria interna deve cobrir?
Toda instrução mapeada para classes do catálogo, testes unitários negativos, pins de dependência, notas de build verificável e riscos residuais conhecidos. Veja Fluxo de Auditoria.
Fail closed significa rejeitar todas as contas desconhecidas?
Significa rejeitar quando prova obrigatória está ausente ou inconsistente: signatário, owner, mint, PDA, oráculo obsoleto ou programa fora da lista errados. Contas opcionais ainda precisam regras explícitas se presentes.
Como segurança interage com paralelismo Sealevel?
Segurança é correção por instrução; paralelismo é sobre conjuntos de lock. Não enfraqueça validação só para passar menos contas.
Autoridade de upgrade deve ser chave quente única?
Evite para valor em risco em produção. Prefira multisig ou governança, monitore upgrades e considere congelar autoridade após maturidade.
Onde oráculos se encaixam em segurança de programa?
Como entradas não confiáveis. Verifique owner do feed, obsolescência e confiança antes de usar preços. Fail closed em dados ausentes ou obsoletos.
Qual é a forma mais rápida de revisar uma instrução nova?
Leia Accounts (signatários, owners, seeds, relações, init vs mut, IDs de programa), depois ordem do handler (matemática, escritas, CPI), depois testes negativos para cada modo de falha.
Relacionados
- Noções Básicas de Segurança - modelo de ameaça e padrões iniciais
- Verificações de Signatário e Owner - essenciais de controle de acesso
- Ataques de Validação de Conta - substituição e confusão de tipo
- Ataques de PDA e Semente - sementes, bumps, impersonação
- Ataques de Reinicialização - guardas init e init_if_needed
- Aritmética e Overflow - matemática verificada e arredondamento
- Riscos de CPI e Reentrância - listas de permissões e CEI
- Catálogo de Ataques Sealevel - checklist de classes clássicas
- Fluxo de Auditoria - congelar, testar, auditar, bounty
- Boas Práticas de Segurança - checklist da seção
Versões da stack: Esta página foi escrita para Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1 e @solana/kit 7.0.0.