Trilha 06 · Memória e execução

Vazamentos e erros de memória

Memory leak, use-after-free, double free, buffer overflow — quatro categorias de erros que custam bilhões por ano em exploits de segurança e crashes de produção. Entender cada um é o primeiro passo para evitá-los.

① Intuição

O que pode dar errado com o heap

Gerenciar memória manualmente é como alugar depósitos: você aluga (malloc), usa, e devolve (free). Se esquecer de devolver, o depósito continua cobrado mas sem uso — isso é um memory leak. Se devolver e depois tentar entrar de novo — isso é use-after-free, e agora outra empresa pode estar lá dentro. Se tentar devolver duas vezes — double free, o administrador do depósito fica confuso e o sistema pode corromper. Se você sair dos seus limites e invadir o depósito do vizinho — isso é buffer overflow.

Em produção, esses erros são frequentemente silenciosos — o programa funciona bem por horas ou dias antes de travar. Pior ainda, use-after-free e buffer overflow são exploits: um atacante pode controlar o que vai ser sobrescrito na memória e sequestrar a execução do programa.

CVEs famosos: Heartbleed (OpenSSL 2014) — buffer over-read vazou dados privados de servidores em todo o mundo. Stagefright (Android 2015) — buffer overflow no parser de mídia, executável via MMS sem interação. Ambos eram erros de memória em C em código amplamente auditado.
② Visualização interativa

Com vazamento vs. sem vazamento

Veja como o uso de memória evolui nos dois programas ao longo do tempo. O da esquerda aloca sem liberar — inevitavelmente esgota a RAM. O da direita libera após cada uso e permanece estável.

Com vazamento
while True:
    buf = malloc(1 KB)
    processa(buf)
    # esqueceu free(buf)!
RAM usada: 5%
Sem vazamento
while True:
    buf = malloc(1 KB)
    processa(buf)
    free(buf)  # ✓
RAM usada: 5%
✓ Uso estável — alloc e free em equilíbrio
ciclo 0
③ Explicação técnica

Os quatro erros clássicos

// Os quatro erros clássicos de memória em C

// 1. Memory leak — aloca mas nunca libera
void vaza() {
    char* buf = malloc(1024);
    usa(buf);
    // esqueceu free(buf)! → 1 KB perdido para sempre
}   // buf sai de escopo, mas o bloco no heap permanece

// 2. Use-after-free (dangling pointer) — usa após liberar
int* p = malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);   // UNDEFINED BEHAVIOR — o bloco pode ser
                        // reutilizado por outra alocação!

// 3. Double free — libera duas vezes
free(p);               // segunda vez → corrompe o heap → crash

// 4. Buffer overflow — escreve além dos limites
char buf[4];
buf[4] = 'X';          // índice 4 está fora! sobrescreve memória adjacente

Como detectar e prevenir

// Ferramentas para detectar erros de memória

// Valgrind — detecta leaks e use-after-free em runtime
// $ valgrind --leak-check=full --error-exitcode=1 ./programa
//   ==1234== LEAK SUMMARY:
//   ==1234==   definitely lost: 1,024 bytes in 1 block
//   ==1234==   at 0x4C2FB0F: malloc (vg_replace_malloc.c)
//   ==1234==   by 0x400537:  vaza (main.c:4)

// AddressSanitizer (ASan) — mais rápido, integrado ao compilador
// $ gcc -fsanitize=address -g programa.c && ./a.out
//   ERROR: AddressSanitizer: heap-use-after-free on address 0x...

// Linguagens com segurança automática:
//  Java / Python / Go / JS  → GC elimina leaks e dangling ptrs
//  Rust                     → borrow checker (tempo de compilação)
//  C++ moderno              → unique_ptr / shared_ptr / RAII
RAII (Resource Acquisition Is Initialization): padrão de C++ onde a aquisição de um recurso (memória, arquivo, socket) acontece no construtor e a liberação no destrutor. Quando o objeto sai de escopo, o destrutor é chamado automaticamente. unique_ptr e shared_ptr usam RAII para gerenciar heap — sem free manual, sem leak, sem double free. É o meio-termo entre C (manual) e GC (automático).
④ Projeto para programar

Caçando bugs de memória

Mini projeto: escreva deliberadamente os quatro erros em um programa C (leak, use-after-free, double free, overflow) e rode cada um com Valgrind ou ASan. Documente a mensagem de erro gerada por cada um. Qual é mais difícil de detectar? Por quê?

Projeto principal: em C++, implemente um SmartPtr<T> (unique pointer simplificado) com constructor, destructor e operator*. O destructor deve chamar delete automaticamente. Adicione um contador de instâncias ativas para verificar que nenhuma instância fica aberta após o escopo terminar. Teste com um tipo que imprime no construtor e no destructor.

Desafio extra: escreva um fuzzer simples: um loop que chama sua função alvo com strings/bytes aleatórios de tamanhos crescentes, rodando com ASan ativo. Se ela crashar, o ASan imprime o stack trace exato. Implemente um parser de URL ingênuo e tente encontrar um buffer overflow nele com seu fuzzer.

⑤ Exercícios rápidos

Teste sua intuição

Uma função aloca memória com malloc, usa-a e retorna sem chamar free(). O que acontece com o bloco?
free(p); printf("%d", *p); — que tipo de erro é esse?
char buf[4]; buf[10] = 'X'; — que problema isso causa?
⑥ Aplicações no mundo real

Onde você encontra isso

🔐

Segurança — exploits de buffer overflow

Um buffer overflow na stack pode sobrescrever o endereço de retorno de uma função. Com controle desse endereço, um atacante redireciona a execução para código malicioso injetado. Técnicas como stack canaries, NX bit e ASLR foram criadas especificamente para dificultar esse ataque — que existe desde os anos 80.

📱

Navegadores e sandboxing

Chrome, Firefox e Safari executam abas em processos separados exatamente porque um use-after-free em código JavaScript pode vazar para o motor de renderização. Se o ataque se limitar a um processo isolado sem permissões, o dano é contido. Dezenas de CVEs de browser por ano são use-after-free em C++.

🦀

A aposta do Rust

A Casa Branca dos EUA publicou em 2024 um guia recomendando que o setor de tecnologia migre de C/C++ para linguagens memory-safe como Rust. O NSA, Google, Microsoft e a Linux Foundation estimam que 70% das vulnerabilidades de segurança críticas são erros de memória — o problema que o borrow checker do Rust resolve em tempo de compilação.

← Anterior: Ponteiros e referências Próxima: Coleta de lixo →