Como Desenvolver Aplicações Mais Seguras com C++
Guia prático de desenvolvimento seguro em C++: previna buffer overflows, use-after-free e bugs de inteiros com C++ moderno, hardening do compilador e scans em CI.
O C++ move navegadores, bancos de dados, motores de jogos, dispositivos embarcados e o núcleo crítico em desempenho de incontáveis sistemas. Esse poder vem com controle direto sobre a memória, e é exatamente nesse controle que mora o perigo. O desenvolvimento seguro em C++ é, em grande parte, uma disciplina de segurança de memória: a linguagem deixa você ler além do fim de um buffer, usar um ponteiro depois que o objeto que ele nomeava desapareceu, ou liberar a mesma alocação duas vezes, e qualquer um desses erros pode entregar a um atacante o controle do seu processo. A boa notícia é que o C++ moderno e uma toolchain bem configurada eliminam a maioria desses riscos quando você os usa de forma deliberada. Este guia percorre os problemas de segurança em C++ que mais importam e as práticas concretas que os neutralizam.
Buffer overflows: stack e heap
Um buffer overflow acontece quando você escreve mais dados em um buffer do que ele comporta, corrompendo a memória adjacente. Na stack, isso pode sobrescrever o endereço de retorno e redirecionar a execução; no heap, pode corromper metadados do alocador ou objetos vizinhos. A causa clássica é copiar para um array de tamanho fixo sem verificar o comprimento, muitas vezes através de strcpy, strcat ou sprintf. A correção é parar de usar raw arrays para dados de tamanho variável. Recorra a std::string e std::vector, que crescem conforme a necessidade, e, quando precisar trabalhar com buffers de tamanho fixo, use operações cientes dos limites e valide cada comprimento antes de copiar.
Use-after-free e ponteiros pendentes
Um use-after-free ocorre quando seu código desreferencia um ponteiro para memória que já foi liberada. A alocação pode ter sido reutilizada para outra coisa, então a leitura ou escrita corrompe dados não relacionados, e atacantes podem preparar o heap para colocar dados controlados ali. Ponteiros pendentes (dangling pointers), incluindo referências a uma variável local que saiu de escopo, são o mesmo bug com outra roupagem. RAII e smart pointers são a resposta: deixe std::unique_ptr ser dono de uma alocação para que ela seja liberada exatamente uma vez quando sair de escopo, e use std::shared_ptr quando a posse for genuinamente compartilhada. Prefira valores e referências com tempos de vida claros a raw owning pointers, e nunca retorne um ponteiro ou referência para uma variável local.
Double free e memória não inicializada
Liberar o mesmo ponteiro duas vezes corrompe o alocador e é explorável por si só. O pareamento manual de new/delete é onde isso se infiltra; os smart pointers tornam esse erro estruturalmente difícil de cometer. A memória não inicializada é uma prima mais discreta: ler uma variável antes de atribuí-la produz valores indeterminados, causando comportamento imprevisível e, às vezes, vazando segredos remanescentes de memória reutilizada. Sempre inicialize as variáveis na declaração, prefira a inicialização com {} e deixe o compilador avisar você com -Wuninitialized.
Integer overflow, underflow e acesso fora dos limites
A aritmética com inteiros de largura fixa faz wrap silenciosamente. Um atacante que consiga forçar o overflow de um cálculo de tamanho pode fazer você alocar um buffer minúsculo e depois escrever uma grande quantidade de dados nele, encadeando um bug de inteiro em um heap overflow. O overflow com sinal é comportamento indefinido, que o otimizador pode explorar de maneiras surpreendentes. Valide valores não confiáveis antes da aritmética, use tipos sem sinal com cuidado, verifique o wrap antes de multiplicar tamanhos e prefira métodos de container como .at() para acesso com verificação de limites, em vez da indexação crua com [] sobre índices não confiáveis.
Bugs de format string e injeção
Passar entrada não confiável como argumento de formato, como em printf(userInput), permite que um atacante leia e escreva na memória através de especificadores de conversão. Sempre passe uma format string fixa e coloque os dados do usuário nos argumentos: printf("%s", userInput). O mesmo cuidado se aplica na fronteira com o sistema operacional. Construir comandos de shell com system() e entrada concatenada é command injection; evite o shell por completo e use APIs que passam os argumentos como um array separado, validando e usando allowlist para qualquer coisa que influencie qual programa será executado.
C++ moderno como postura de segurança
O fio condutor da codificação segura em C++ é preferir as abstrações seguras da linguagem aos seus primitivos de baixo nível. Use RAII para que cada recurso tenha um dono e uma liberação determinística. Use smart pointers em vez de gerenciamento manual de memória. Use std::string e std::vector em vez de C strings e raw arrays. Use std::span e acessadores com verificação de limites para manter a indexação honesta. Ative um padrão moderno da linguagem e deixe a biblioteca padrão carregar o peso para o qual foi projetada. Estas não são preferências estilísticas; cada uma delas remove uma categoria de bug explorável.
Hardening do compilador e fuzzing
Sua toolchain é uma ferramenta de segurança. Compile com um conjunto forte de warnings e trate warnings como erros, para que padrões arriscados não possam se acumular. Durante o desenvolvimento e os testes, faça o build com AddressSanitizer e UndefinedBehaviorSanitizer para capturar overflows, use-after-free e comportamento indefinido em tempo de execução. Nos builds de release, ative stack canaries, FORTIFY_SOURCE, executáveis independentes de posição (PIE) e realocações somente leitura (RELRO), para que os bugs que de fato escapem sejam muito mais difíceis de explorar. Depois adicione fuzzing: alimente seus parsers e handlers de formato com entrada randomizada e malformada, para que a máquina encontre os casos extremos que seus testes jamais imaginaram.
Risco de dependências e cadeia de suprimentos
Projetos em C++ trazem bibliotecas por meio de gerenciadores de pacotes como Conan e vcpkg, por meio de pacotes do sistema e por meio de código vendorizado copiado diretamente para a árvore do projeto. Cada um deles é código que você distribui e do qual precisa dar conta. Bibliotecas vendorizadas são especialmente fáceis de esquecer depois de copiadas, então ficam desatualizadas e carregam vulnerabilidades conhecidas por anos. Rastreie tudo de que você depende, execute software composition analysis para sinalizar componentes com CVEs conhecidos e mantenha-os atualizados. O static application security testing complementa isso capturando padrões inseguros no seu próprio código.
Se você quiser um panorama mais completo de como esses controles se encaixam, veja nosso guia de secure software development, a diferença entre SAST and DAST e o OWASP Top 10, cuja edição de 2025 mapeia bem os riscos acima.
Um checklist rápido de C++ seguro
- Substitua raw arrays e C strings por
std::vectorestd::string. - Proíba
strcpy,strcat,sprintfegetsem código novo. - Seja dono de cada alocação com
std::unique_ptroustd::shared_ptr; evitenew/deletemanuais. - Inicialize cada variável na declaração.
- Valide inteiros antes da aritmética de tamanho; use
.at()para índices não confiáveis. - Nunca passe entrada não confiável como format string ou para dentro de um comando de shell.
- Faça o build com warnings-as-errors, ASan e UBSan; teste continuamente.
- Faça o hardening dos releases com stack canaries, FORTIFY_SOURCE, PIE e RELRO.
- Faça fuzzing em parsers e em qualquer coisa que toque em entrada não confiável.
- Execute SAST, software composition analysis e secret scanning no CI.
Como a Rainforest ajuda
A codificação segura em C++ é um hábito, e hábitos precisam de reforço. A Rainforest reúne SAST e software composition analysis no seu pipeline, sinalizando padrões inseguros de memória e dependências vulneráveis à medida que o código é commitado, com orientações claras sobre impacto e remediação. Isso significa que sua equipe captura o buffer overflow, o ponteiro pendente ou a biblioteca vendorizada desatualizada antes que ela vá para produção, sem desacelerar a entrega. Explore application security testing e software composition analysis, ou book a demo para ver como isso se encaixa no seu fluxo de trabalho em C++.
Perguntas frequentes
Por que o C++ é propenso a vulnerabilidades de segurança?
O C++ dá a você controle direto sobre a memória, sem verificação automática de limites ou garbage collection, então erros com ponteiros, buffers e alocação manual não são capturados pela linguagem. Esse controle possibilita o desempenho do C++, mas também significa que bugs de segurança de memória como buffer overflows e use-after-free são fáceis de introduzir e frequentemente exploráveis. O C++ moderno e práticas disciplinadas fecham a maior parte dessa lacuna.
O que é um buffer overflow?
Um buffer overflow é escrever mais dados em um buffer do que o tamanho para o qual ele foi dimensionado, de modo que o excesso corrompe a memória adjacente. Na stack, pode sobrescrever um endereço de retorno e sequestrar a execução; no heap, pode corromper metadados do alocador ou objetos próximos. Usar operações com verificação de comprimento e containers que crescem como std::vector e std::string previne isso.
O que é use-after-free?
Use-after-free é desreferenciar um ponteiro para memória que já foi liberada. Como a alocação pode ter sido reutilizada, o acesso lê ou escreve dados não relacionados, e atacantes podem fazer com que esses dados sejam os deles, levando à execução de código. RAII e smart pointers, que amarram o tempo de vida de uma alocação a um dono claro, são a defesa principal.
Como eu torno o C++ seguro em relação à memória?
Prefira as abstrações do C++ moderno aos primitivos crus: use smart pointers em vez de new/delete manuais, use std::string e std::vector em vez de C strings e raw arrays, inicialize cada variável e use acesso com verificação de limites para índices não confiáveis. Reforce isso com AddressSanitizer e UndefinedBehaviorSanitizer durante os testes e com hardening do compilador nos builds de release.
Como eu faço scan de código C++ em busca de vulnerabilidades?
Combine várias ferramentas: SAST para padrões inseguros no seu próprio código, software composition analysis para vulnerabilidades conhecidas em bibliotecas trazidas via Conan, vcpkg, pacotes do sistema ou cópias vendorizadas, sanitizers como ASan e UBSan durante os testes, fuzzing para casos extremos e secret scanning para credenciais vazadas. Executar tudo isso automaticamente no CI torna o scan contínuo em vez de uma revisão pontual.

Escrito por
Bruno Baldo
CMO
Um pouco de marketing e um pouco de curiosidade e temos a receita pra criar um apaixonado por cyber!

Desenvolvimento seguro de software: práticas para um SDLC seguro
Guia prático de desenvolvimento seguro: SDLC seguro fase a fase, codificação segura, shift-left, DevSecOps, LGPD e automação no CI/CD.

SAST vs DAST: as diferenças e quando usar cada um
SAST vs DAST: como os testes estático e dinâmico de segurança de aplicações diferem, quando usar cada um e por que os dois se complementam.
