Compartir por


Error: container-overflow

Error de saneador de dirección: desbordamiento del contenedor

En Visual Studio 2022, versión 17.2 y posteriores, la biblioteca estándar (STL) de Microsoft Visual C++ está parcialmente habilitada para trabajar con AddressSanitizer. Los siguientes tipos de contenedor tienen anotaciones para detectar problemas de acceso a memoria:

Tipo de contenedor estándar Deshabilitar macro de anotaciones Compatible con la versión
std::vector _DISABLE_VECTOR_ANNOTATION Visual Studio 2022 17.2
std::string _DISABLE_STRING_ANNOTATION Visual Studio 2022 17.6

Hay comprobaciones para asegurarse de que no hay infracciones de una regla de definición (ODR). Una infracción de ODR se produce cuando una unidad de traducción anota un tipo estándar, como vector, con anotaciones ASan, pero no otra unidad de traducción. En este ejemplo, el enlazador podría ver simultáneamente una declaración de vector<int>::push_back que tiene anotaciones de corrector de direcciones y otra declaración de vector<int>::push_back que no lo hace. Para evitar este problema, cada biblioteca estática y objeto usado para vincular el binario también debe habilitar las anotaciones de ASan. De hecho, debe compilar esas bibliotecas estáticas y objetos con AddressSanitizer habilitado. La combinación de código con diferentes valores de anotación provoca un error:

my_static.lib(my_code.obj) : error LNK2038: mismatch detected for 'annotate_vector': value '0' doesn't match value '1' in main.obj

Para resolver este error, deshabilite las anotaciones en todos los proyectos que usan la macro correspondiente o compile cada proyecto mediante /fsanitize=address anotaciones y habilitadas. (Las anotaciones están habilitadas de forma predeterminada).

Ejemplo: Acceso a la memoria reservada en un std::vector

// Compile with: cl /EHsc /fsanitize=address /Zi
#include <vector>

int main() {   
    // Create a vector of size 10, but with a capacity of 20.    
    std::vector<int> v(10);
    v.reserve(20);

    // In versions prior to 17.2, MSVC ASan does NOT raise an exception here.
    // While this is an out-of-bounds write to 'v', MSVC ASan
    // ensures the write is within the heap allocation size (20).
    // With 17.2 and later, MSVC ASan will raise a 'container-overflow' exception:
    // ==18364==ERROR: AddressSanitizer: container-overflow on address 0x1263cb8a0048 at pc 0x7ff6466411ab bp 0x005cf81ef7b0 sp 0x005cf81ef7b8
    v[10] = 1;

    // Regardless of version, MSVC ASan DOES raise an exception here, as this write
    // is out of bounds from the heap allocation.
    v[20] = 1;
}

Para compilar y probar este ejemplo, ejecute los siguientes comandos en una ventana del símbolo del sistema para desarrolladores de Visual Studio 2022, versión 17.2 o posterior:

cl /EHsc example1.cpp /fsanitize=address /Zi
devenv /debugexe example1.exe

Resultado del acceso reservado a la memoria en un std::vector

Captura de pantalla del depurador, donde se muestra el error container-overflow en el ejemplo 1.

Asignadores personalizados y desbordamiento de contenedor

Las comprobaciones de desbordamiento del contenedor de Sanitizer de direcciones admitenstd::allocator no asignadores. Sin embargo, dado que AddressSanitizer no sabe si un asignador personalizado se ajusta a los requisitos addressSanitizer, como alinear asignaciones en límites de 8 bytes, o no colocar datos entre el final de la asignación y el siguiente límite de 8 bytes, es posible que no siempre pueda comprobar que los accesos en el último extremo de una asignación son correctos.

AddressSanitizer marca bloques de memoria en fragmentos de 8 bytes. No puede colocar bytes inaccesibles antes de los bytes accesibles en un único fragmento. Es válido tener 8 bytes accesibles en un fragmento o 4 bytes accesibles seguidos de 4 bytes inaccesibles. Cuatro bytes inaccesibles no pueden ir seguidos de 4 bytes accesibles.

Si el final de una asignación de un asignador personalizado no se alinea estrictamente con el final de un fragmento de 8 bytes, AddressSanitizer debe asumir que el asignador realiza los bytes entre el final de la asignación y el final del fragmento disponible para el asignador o el usuario en el que escribir. Por lo tanto, no puede marcar los bytes en el fragmento final de 8 bytes como inaccesible. En el ejemplo siguiente de un vector objeto que asigna memoria mediante un asignador personalizado, "?" hace referencia a datos no inicializados y "-" hace referencia a la memoria inaccesible.

std::vector<uint8_t, MyCustomAlloc<uint8_t>> v;
v.reserve(20);
v.assign({0, 1, 2, 3});
// the buffer of `v` is as follows:
//    | v.data()
//    |       | v.data() + v.size()
//    |       |                                     | v.data() + v.capacity()
//  [ 0 1 2 3 ? ? ? ? ][ ? ? ? ? ? ? ? ? ][ ? ? ? ? - - - - ]
//        chunk 1            chunk 2            chunk 3

En el ejemplo anterior, el fragmento 3 tiene 4 bytes de memoria que se supone que son inaccesibles porque se encuentran entre el final de la asignación de los 20 bytes reservados (v.reserve(20)) y el final de la tercera agrupación lógica de 8 bytes (recuerde que AddressSanitizer marca bloques de memoria en fragmentos de 8 bytes).

Idealmente, marcaríamos la memoria de sombra, que Address Sanitizer reserva para cada bloque de memoria de 8 bytes para realizar un seguimiento de los bytes de ese bloque de 8 bytes son válidos y que no son válidos (y por qué), de modo que v.data() + [0, v.size()) son accesibles y v.data() + [v.size(), v.capacity()) son inaccesibles. Tenga en cuenta el uso de la notación de intervalo aquí: '[' significa inclusivo y ')' significa exclusivo de. Si el usuario usa un asignador personalizado, no sabemos si la memoria después v.data() + v.capacity() es accesible o no. Debemos asumir que es. Preferimos marcar esos bytes como inaccesibles en la memoria de sombras, pero debemos marcarlos como accesibles para que siga siendo posible acceder a esos bytes después de la asignación.

std::allocator usa la _Minimum_asan_allocation_alignment variable de miembro estático para indicar vector y string que pueden confiar en que el asignador no coloque los datos justo después de la asignación. Esto garantiza que el asignador no usará la memoria entre el final de la asignación y el final del fragmento. Por lo tanto, esa parte del fragmento puede estar marcada como inaccesible por address sanitizer para detectar saturaciones.

Si desea que la implementación confíe en que el asignador personalizado controla la memoria entre el final de la asignación y el final del fragmento para que pueda marcar esa memoria como inaccesible y detectar saturaciones, establezca _Minimum_asan_allocation_alignment en la alineación mínima real. Para que AddressSanitizer funcione correctamente, la alineación debe ser al menos 8.

Consulte también

Introducción a AddressSanitizer
Problemas conocidos de AddressSanitizer
Referencia de lenguaje y compilación de AddressSanitizer
Referencia del entorno de ejecución addressSanitizer
Bytes de sombra addressSanitizer
Pruebas distribuidas o en la nube addressSanitizer
Integración del depurador AddressSanitizer
Ejemplos de errores addressSanitizer