A segurança de programas Solana é segurança de conta. Chamadores escolhem quais contas e programas entram em uma instrução; Sealevel impõe bloqueios, regras de escrita de propriedade e orçamentos de computação, não o saldo do seu cofre, lista de administradores 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 proprietário, ataques PDA e de semente, reinicialização, aritmética, risco de CPI e reentrância, e um fluxo de auditoria prático no Agave 4.1.1.
Toda instrução é uma interface adversarial: prove quem assinou, quem possui cada conta, qual PDA e tipo você está acessando, depois modifique o estado com matemática verificada e ordem CPI segura.
Insight: Exploits raramente precisam de uma quebra criptográfica nova. Eles substituem um cofre errado, pulam um signatário, reinicializam um administrador, fazem wrap de um saldo ou chamam via CPI um programa malicioso que retorna "success."
Conceitos Chave:validação de conta, verificações de signatário / proprietário, sementes e bump de PDA, guardas de reinicialização, aritmética verificada, listas de permissões de CPI, checks-effects-interactions (CEI), design fail-closed, fluxo de auditoria.
Quando Usar Este Modelo: Ao projetar novas instruções, revisar #[derive(Accounts)], preparar para auditoria externa, triar achados ou ensinar o modelo de ameaças Solana.
Limitações/Trade-offs: Restrições do Anchor capturam muitas classes, mas não regras de negócio; UncheckedAccount e remaining_accounts reintroduzem risco; revisões de segurança não substituem fuzzing ou revisão de design econômico.
Sealevel executa programas como executáveis sem estado. O estado durável reside em contas que a transação lista antecipadamente. Clientes (ou atacantes) fornecem essas contas, flags de signatário e flags de escrita (is_writable). O runtime verifica se os signatários declarados realmente assinaram e se apenas o programa proprietário pode escrever dados da conta. Ele não sabe que "esta TokenAccount deve corresponder ao mint do cofre" ou "apenas a autoridade 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 budgetYour program must guarantee: right identity signed | right owners and types | right PDAs | right amounts | safe CPIs
Fail closed é a postura padrão: se a validação estiver incompleta, rejeite com um erro tipado. Nunca assuma "clientes honestos" ou "nosso frontend só constrói transações boas." Frontends e indexadores não fazem parte da fronteira de confiança; verificações on-chain sim.
O Anchor 0.32.1 codifica verificações comuns (Signer, Account<T>, seeds, has_one, Program<T>). Isso não constitui uma revisão completa. Lacunas aparecem com AccountInfo, UncheckedAccount, remaining_accounts, init_if_needed sem guarda, e restrições de regras de negócio que não podem ser expressas.
Modelo de ameaça para um programa típico de cofre ou mercado:
Atacantes passam contas que parecem utilizáveis: mesmo tamanho, de propriedade do seu programa, ou deserializáveis em um tipo incorreto. Validação significa mais do que "deserializa".
Checklist por papel da conta:
Proprietário - programa esperado (Account<T> para seus tipos; require_keys_eq! para SPL Token, Sysvar, etc.).
Tipo / discriminador - discriminador de conta Anchor ou constante nativa para que Vault nunca seja User.
Restrições relacionais - o mint corresponde ao cofre, o proprietário da conta de token é a PDA do cofre, a configuração corresponde ao mercado.
Mutabilidade - apenas as contas que você pretende modificar são mut.
PDA canônica - seeds + bump armazenado para que o endereço seja derivado, não escolhido livremente.
#[derive(Accounts)]pub struct Deposit<'info> { // `has_one = mint` exigiria um campo de conta `mint` nesta struct; a // restrição explícita de mint abaixo cobre a ligação sem um. #[account(mut, has_one = authority)] 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 brutos pulam as verificações de proprietário e discriminador até que você as adicione. Trate cada campo assim como uma falha até que seja provado validado.
Signatário: qualquer caminho que move fundos, altera autoridade, fecha contas ou atualiza configurações sensíveis requer uma assinatura criptográfica da chave correta (ou uma PDA assinada via invoke_signed com sementes controladas pelo programa).
Proprietário: apenas o programa proprietário pode reescrever dados. Ler uma conta que seu programa não possui sem verificar o proprietário é como a confusão de tipo e a entrada de mints falsos. Para contas de token, o proprietário deve ser o programa Token ou Token-2022 que você pretende; para seu estado, o proprietário deve ser seu program_id.
Padrão crítico comum: o cofre armazena authority: Pubkey, mas a instrução nunca exige authority como Signer ou nunca o vincula com has_one.
PDAs são a autoridade e a identidade do endereço. Sementes fracas ou ambíguas permitem que um atacante apresente uma PDA diferente que seu programa ainda "aceita".
Regras gerais:
Inclua todas as dimensões de identidade nas sementes (usuário, mint, ID do mercado, prefixo de função).
Armazene o bump canônico na inicialização; reutilize bump = account.bump depois (sem busca ambígua de bump em caminhos críticos).
Assine CPIs apenas com as mesmas sementes usadas para criar a PDA.
Não permita que usuários forneçam bytes de semente arbitrários sem separação de domínio (b"vault", tags de versão).
A inicialização (init) escreve discriminadores, autoridades e saldos. Reexecutar a inicialização (ou init_if_needed sem uma flag) pode redefinir o administrador para o atacante ou reativar uma forma de conta fechada.
Prefira init único para estado permanente. Se precisar de init_if_needed, exija !is_initialized (ou equivalente) antes de escrever campos de autoridade, e nunca reabra contas fechadas sem um caminho deliberado e auditado. Ao fechar, zere os dados / discriminador e transfira os lamports para que a conta não possa ser tratada como estado ativo.
Builds de release BPF fazem wrap em operações +, -, * simples. A matemática de cofres, cunhagem de shares, acúmulo de taxas e fórmulas AMM precisam de checked_* (ou operações saturating onde intencional) e frequentemente intermediários u128 antes de converter de volta para u64.
O arredondamento deve favorecer o protocolo em cunhagens e resgates voltados ao usuário, para que poeira não possa ser explorada para obter valor gratuito. Divisão por zero e casos extremos de pool vazio são bugs de segurança, não meros problemas de UX.
A invocação entre programas estende a confiança a outro programa. Riscos:
ID de programa incorreto - um "programa de token" impostor não faz nada ou rouba.
Contas remanescentes não validadas - contas de callback controladas pelo atacante.
Estado após CPI - o chamado não pode reentrar em seu programa (o runtime rejeita isso com ReentrancyNotAllowed), mas pode modificar contas que você já desserializou, então sua cópia em memória fica desatualizada. Uma instrução de nível superior posterior na mesma transação também pode ler saldos que ainda parecem pré-atualização.
Mitigações: liste permissões de chamados com Program<'info, T> ou require_keys_eq!; atualize o estado do programa antes da CPI externa (CEI); evite callbacks não confiáveis, a menos que você projete verificações explícitas de dívida estilo flash-loan; passe apenas as contas que o chamado precisa.
vault.balance = vault.balance.checked_sub(amount).ok_or(ErrorCode::Overflow)?;// efeitos primeiro, depois interaçãotoken::transfer(cpi_ctx, amount)?;
Solana não é idêntica à reentrância EVM, mas ordem de chamada incorreta e alvos CPI não confiáveis ainda resultam em perda de fundos.
O trabalho de segurança é tanto um processo quanto código:
Congele uma tag RC, bloqueie dependências (Anchor 0.32.1, pins Agave/CLI), documente o modelo de ameaça.
Mapeie internamente cada instrução para as classes do Catálogo de Ataques Sealevel (signatário, proprietário, substituição, PDA, reinit, matemática, CPI, fechamento, oráculo).
Testes negativos no LiteSVM / testes Anchor: signatário errado, mint errado, programa errado, inicialização dupla, quantidades de overflow.
Fuzz caminhos com uso intensivo de parsing e matemática quando a complexidade justificar.
Build verificável para que os auditores correspondam o código-fonte ao hash on-chain.
Auditoria externa + ciclo de correção; compare novamente após cada alteração em Accounts ou aritmética.
Bug bounty e plano de upgrade/rollback para risco residual.
Proprietário + discriminador para Account<T>, muitas restrições de PDA
Regras de negócio, caminhos Unchecked
Padrão para todas as contas de estado
require_* manual
Invariantes relacionais e econômicas
Fácil de omitir em novas instruções
Toda revisão de novo handler
CEI + lista de permissões de programa
Roubo clássico de CPI / programas impostores
Bugs de design econômico
Qualquer movimentação de token ou callback
Matemática verificada + testes de propriedade
Limites de overflow e matemática de share
Jogos de oráculo e MEV
Cofres, AMMs, empréstimos
Auditoria externa + bounty
Novos olhares, classes conhecidas do catálogo
Ataques de orçamento infinito, upgrades futuros
Pré-lançamento e upgrades importantes
Oráculos são entradas não confiáveis: verifique o proprietário do feed, a atualidade e a confiança; falhe fechado em dados ruins.
Extensões Token-2022 (hooks, delegados permanentes) alteram quem pode mover tokens. Fixe o ID do programa de token; não assuma a semântica clássica do SPL Token.
Fechamento e aluguel (rent): exija o signatário e o destino corretos; zere os dados para que as contas não possam ser reativadas com verificações fracas. Prefira close = do Anchor com restrições.
Autoridade de upgrade é a raiz. 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 apenas usuários honestos. 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 em validadores locais. Trate cada nova instrução como uma nova superfície de ataque.
"O runtime valida as regras do meu cofre." Ele valida assinaturas, bloqueios, proprietários para escritas e CU. As regras do protocolo são exclusivamente suas.
"Anchor significa que estamos seguros." Anchor codifica verificações comuns; contas Unchecked, restrições incorretas e bugs de matemática ainda são lançados.
"Se desserializa, a conta está ok." Desserialização não é vínculo de mint, autoridade ou identidade PDA.
"PDAs não podem ser falsificadas." Sementes incorretas ou incompletas produzem uma PDA válida diferente sob seu programa. As sementes definem a política de segurança.
"init_if_needed é sempre seguro." Sem uma flag de inicialização (ou equivalente), a reinicialização redefine campos críticos.
"Rust previne overflow on-chain." O wrap de release BPF é real; use matemática verificada para caminhos de valor.
"CPI para Token é sempre seguro." Apenas se o ID do programa for o programa Token/Token-2022 real e as contas corresponderem às restrições de mint e autoridade.
"Corrigiremos a segurança na auditoria." Auditorias são amostrais. Projete fail-closed primeiro; auditorias capturam bugs residuais.
"Contas somente leitura não podem nos prejudicar." Configurações falsas, oráculos e mints são frequentemente somente leitura, mas controlam totalmente os resultados econômicos.
"Fechar uma conta encerra todo o risco." Fechamento incompleto ou destino incorreto cria roubo de aluguel (rent) e problemas de reativação.
Qual é a superfície de ataque Sealevel para um programa?
Tudo o que o chamador pode escolher: quais contas e programas aparecem, quem assina, dados da instrução e alvos de CPI. O runtime não impõe 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 um Signer obrigatório (ou caminho invoke_signed de PDA), qualquer chave de conta pode ser nomeada como "autoridade", enquanto apenas o pagador de taxa realmente assina. Isso permite saques não autorizados e tomada de controle administrativo. Veja Verificações de Signatário e Proprietário.
O que é substituição de conta?
Passar um cofre, conta de token, mint ou configuração diferente do que o protocolo pretende, muitas vezes ainda de propriedade de um programa plausível. Corrija com restrições relacionais (has_one, igualdade de mint, sementes). Veja Ataques de Validação de Conta.
Como funcionam os ataques de semente PDA?
Se as sementes omitirem campos de identidade ou os bumps não forem canônicos, os atacantes derivam PDAs alternativas que sua instrução ainda aceita, personificando cofres ou escrows. Armazene os bumps e fixe listas completas de sementes. Veja Ataques de PDA e Semente.
Quando a reinicialização é possível?
Quando init pode ser executado novamente em estado ativo: init_if_needed sem guardas, discriminadores ausentes, ou contas fechadas que são recriadas sob verificações fracas. Veja Ataques de Reinicialização.
Solana precisa de aritmética verificada?
Sim, para caminhos de valor. A 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 um contexto de CPI Solana
Revisado por Chris St. John·Última atualização: 15 de jul. de 2026