Ir para o conteúdo

Última atualização:2026-10-12

AddressSanitizer: Detecção de Memória

C e C++ são linguagens muito inseguras e propensas a erros. O Address Sanitizer é uma ferramenta desenvolvida pelo Google para detectar erros de acesso à memória, como use-after-free e vazamentos de memória. Está integrado ao GCC em versões >= 4.8 e pode ser usado em código C e C++. O Address Sanitizer usa instrumentação em tempo de execução para rastrear alocações de memória, o que significa que você deve compilar seu código com o AddressSanitizer para aproveitar plenamente seus recursos.

Este artigo apresentará a ferramenta de detecção de memória AddressSanitizer com base nesses 8 cenários.

1.Use after free

Tome o código abaixo como exemplo. Primeiro, a palavra-chave new aloca espaço de memória para um inteiro e atribui seu endereço ao ponteiro ptr. Em seguida, a palavra-chave delete libera essa memória. Porém, após a liberação, o programa ainda tenta acessar a memória já liberada através do ponteiro ptr e atribui o valor 10 a essa posição — esse é o erro de uso após liberação.

#include <iostream>

int main() {
  int* ptr = new int;
  delete ptr;
  *ptr = 10;  // Use after free
  return 0;
}

Ao compilar, adicione a opção abaixo.

-fsanitize=address

Execute o binário gerado e obtenha o resultado a seguir.

heap use after free

2.heap buffer overflow

Primeiro, a palavra-chave new aloca no heap um array com dois inteiros e atribui o endereço inicial do array ao ponteiro arr. Em seguida, o programa tenta armazenar o valor 10 na terceira posição do array (índice 2), o que ultrapassa o intervalo válido do array, causando um estouro de buffer no heap.

#include <iostream>

int main() {
  int* arr = new int[2];
  arr[2] = 10;  // Heap buffer overflow
  delete[] arr;
  return 0;
}

Uso igual ao anterior:

g++-13 heap_buffer_overflow.cc  -Wall -g -fsanitize=address 

Depois:

./a.out

Obtemos o resultado de detecção abaixo.

heap_buffer_overflow

3.stack buffer overflow

Na função vulnerableFunction, é definido um array de caracteres buffer com 8 caracteres. Em seguida, usa-se std::cin para ler dados da entrada padrão para buffer, sem limitar o tamanho da entrada. Se o número de caracteres exceder o tamanho de buffer, ocorre um estouro de buffer na pilha.

void vulnerableFunction() {
  char buffer[8];
  std::cin >> buffer;  // Stack buffer overflow
  std::cout << "You entered: " << buffer << std::endl;
}

Uso igual ao anterior.

stack_buffer_overflow

4.global buffer overflow

O buffer global (o array buffer) é definido no escopo global do programa, sem limitação suficiente do tamanho da entrada. Se o número de caracteres exceder o tamanho do buffer, ocorre um estouro de buffer global. Semelhante ao estouro na pilha, o estouro de buffer global pode causar instabilidade e problemas de segurança.

#include <iostream>

char buffer[8];

int main() {
    std::cin >> buffer; // 全局缓冲区溢出错误
    std::cout << "You entered: " << buffer << std::endl;
    return 0;
}

Uso igual ao anterior; obtemos:

global_buffer_overflow

5.use after return

Na função f, o endereço da variável local i é retornado, mas em main tenta-se acessar a variável local já destruída através do ponteiro retornado. Nesse cenário, é possível detectar no binário configurando ASAN_OPTIONS=detect_stack_use_after_return=1.

g++-13 stack_use_after_return.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_return=1 ./a.out

Código:

#include <iostream>

int *f() {
  int i = 42;
  int *p = &i;
  return p;
}

int main() { return *f(); }

Obteremos:

stack_use_after_return

6.use after scope

O endereço da variável local x é atribuído ao ponteiro p; porém, após sair do escopo interno, tenta-se acessar a variável local x já fora de escopo através do ponteiro p, causando um erro de uso fora de escopo. Isso também é comportamento indefinido.

#include <iostream>
int main() {
  int* p = 0;
  {
    int x = 0;
    p = &x;
  }
  *p = 5;
  return 0;
}

Compilação e execução:

g++-13 stack_use_after_scope.cc -Wall -g -fsanitize=address
ASAN_OPTIONS=detect_stack_use_after_scope=0  ./a.out 

Em testes, o clang++ detecta; o g++ não consegue detectar.

stack_use_after_scope

7.Initialization order bugs

Em dois arquivos-fonte diferentes há inicialização da variável global a, mas a ordem de inicialização é incerta. No arquivo init_order_b.cc, tenta-se usar o valor da variável a; devido à ordem de inicialização inconsistente, pode ocorrer comportamento indefinido.

O código é o seguinte:

// init_order_a.cc
int duplicate(int n) { return n * 2; }
auto a = duplicate(2);

// init_order_b.cc
#include <iostream>

extern int a;
auto b = a;  

int main() {
  std::cout << b << std::endl;
  return 0;
}

Quando escrevemos código assim, o resultado é indeterminado: pode ser 4 ou 0, dependendo da ordem de inicialização.

Por exemplo:

clang++ init_order_a.cc init_order_b.cc  -Wall -g -fsanitize=address

Assim a saída é 4; compilando primeiro b e depois a, obtém-se 0.

clang++ init_order_b.cc init_order_a.cc  -Wall -g -fsanitize=address

Quando esse tipo de problema ocorre, podemos usar o AddressSanitizer para detectar a ordem de inicialização.

ASAN_OPTIONS=check_initialization_order=true ./a.out 

init_order

8.memory leak

A palavra-chave new aloca dinamicamente memória para um inteiro, mas antes do término do programa a palavra-chave delete não é usada para liberá-la, causando vazamento de memória.

#include <iostream>

int main() {
  int* ptr = new int;
  // Missing delete ptr; // Memory leak
  return 0;
}

Configure ASAN_OPTIONS=detect_leaks=1.

ASAN_OPTIONS=detect_leaks=1 ./a.out

memory_leak

Tópicos relacionados

Comentários