Saltar a contenido

Última actualización:2026-10-12

Detección de memoria con AddressSanitizer

C y C++ son lenguajes de programación muy inseguros y propensos a errores; Address Sanitizer es una herramienta desarrollada por Google para detectar errores de acceso a memoria, como el uso tras liberación (use-after-free) y las fugas de memoria. Está integrada en GCC desde la versión >= 4.8 y puede usarse con código C y C++. Address Sanitizer emplea instrumentación en tiempo de ejecución para rastrear las asignaciones de memoria, lo que significa que debes compilar tu código con AddressSanitizer para aprovechar al máximo sus funciones.

Este artículo presentará la herramienta de detección de memoria AddressSanitizer a partir de los 8 escenarios anteriores.

1. Use after free

Tomemos el código siguiente como ejemplo. Primero, con la palabra clave new se asigna un espacio de memoria para un entero y su dirección se guarda en el puntero ptr. Después, la palabra clave delete libera esa memoria. Sin embargo, tras liberarla, el programa sigue intentando acceder mediante el puntero ptr a la memoria ya liberada y asigna el valor 10 a esa posición: eso es un error de uso tras liberación.

#include <iostream>

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

Al compilar, añade la opción siguiente.

-fsanitize=address

Ejecuta el binario generado y obtendrás el siguiente resultado.

heap use after free

2. heap buffer overflow

Primero, con la palabra clave new se asigna en el montículo un array de dos enteros y la dirección inicial del array se guarda en el puntero arr. Después, el programa intenta almacenar el valor 10 en la tercera posición del array (índice 2), lo cual excede el rango válido del array y provoca un error de desbordamiento del búfer del montículo.

#include <iostream>

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

Igual que antes:

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

Después:

./a.out

Y se obtiene el siguiente resultado de detección.

heap_buffer_overflow

3. stack buffer overflow

En la función vulnerableFunction se define un array de caracteres buffer de 8 caracteres. Después, se usa std::cin para leer de la entrada estándar y almacenar los datos en buffer, sin limitar el tamaño de la entrada. Si el número de caracteres introducidos supera el tamaño de buffer, se produce un error de desbordamiento del búfer de pila.

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

El uso es el mismo que antes.

stack_buffer_overflow

4. global buffer overflow

El búfer global (el array buffer) está definido en el ámbito global del programa y no sufre ninguna restricción suficiente del tamaño de entrada. Si el número de caracteres introducidos supera el tamaño del búfer, se produce un error de desbordamiento del búfer global. Al igual que el desbordamiento del búfer de pila, el desbordamiento del búfer global puede acarrear problemas de estabilidad y seguridad del programa.

#include <iostream>

char buffer[8];

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

El uso es el mismo que antes, y se obtiene:

global_buffer_overflow

5. use after return

En la función f se devuelve la dirección de la variable local i, pero en la función main se intenta acceder mediante el puntero devuelto a una variable local ya destruida. Ante este escenario, se puede inspeccionar el binario 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(); }

Obtendremos:

stack_use_after_return

6. use after scope

La dirección de la variable local x se asigna al puntero p; sin embargo, tras salir de ese ámbito interno se intenta acceder mediante el puntero p a la variable local x, ya fuera de ámbito, lo que provoca un error de uso fuera de ámbito. Esto también es comportamiento indefinido.

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

Compilación y ejecución:

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

En la práctica funciona con clang++; g++ no consigue detectarlo.

stack_use_after_scope

7. Initialization order bugs

En dos archivos fuente distintos existe la inicialización de la variable global a, pero el orden de inicialización es indeterminado. En el archivo init_order_b.cc se intenta usar el valor de la variable a; sin embargo, debido a la inconsistencia del orden de inicialización, puede provocar un comportamiento indefinido.

El código es el siguiente:

// 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;
}

Cuando escribimos código así, su resultado es indeterminado: puede ser 4 o 0, según el orden de inicialización.

Por ejemplo:

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

Así la salida es 4, mientras que compilando primero b y después a se obtiene 0.

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

Cuando surge este problema, podemos usar AddressSanitizer para comprobar el orden de inicialización.

ASAN_OPTIONS=check_initialization_order=true ./a.out 

init_order

8. memory leak

Con la palabra clave new se asigna dinámicamente memoria para un entero, pero el programa no libera esa memoria con delete antes de terminar, lo que provoca una fuga de memoria.

#include <iostream>

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

Configura ASAN_OPTIONS=detect_leaks=1.

ASAN_OPTIONS=detect_leaks=1 ./a.out

memory_leak

Temas relacionados

Comentarios