Erro: container-overflow

Erro do sanitizador de endereço: estouro de contêiner

No Visual Studio 2022 versão 17.2 e posterior, a STL (biblioteca padrão) do Microsoft Visual C++ é parcialmente habilitada para trabalhar com o AddressSanitizer. Os seguintes tipos de contêiner têm anotações para detectar problemas de acesso à memória:

Tipo de contêiner padrão Desabilitar macro de anotações Com suporte na versão
std::vector _DISABLE_VECTOR_ANNOTATION Visual Studio 2022 17.2
std::string _DISABLE_STRING_ANNOTATION Visual Studio 2022 17.6

Há verificações para garantir que não haja violações de ODR (regra de definição única). Uma violação de ODR ocorre quando uma unidade de tradução anota um tipo padrão, como vector, com anotações ASan, mas outra unidade de tradução não. Neste exemplo, o vinculador pode ver simultaneamente uma declaração que tem anotações do vector<int>::push_back Address Sanitizer e outra declaração que vector<int>::push_back não. Para evitar esse problema, cada biblioteca estática e objeto usado para vincular o binário também deve habilitar anotações ASan. Efetivamente, você deve compilar essas bibliotecas e objetos estáticos com o AddressSanitizer habilitado. A combinação de código com diferentes configurações de anotação causa um erro:

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 esse erro, desative as anotações em todos os projetos que usam a macro correspondente ou crie cada projeto usando /fsanitize=address as anotações habilitadas. (As anotações são habilitadas por padrão).

Exemplo: Acessar memória reservada em um 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 e testar este exemplo, execute os seguintes comandos em uma janela do prompt de comando do desenvolvedor do Visual Studio 2022 versão 17.2 ou posterior:

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

Resultado de erro do acesso reservado à memória em um std::vector

Captura de tela do depurador exibindo erro de estouro de buffer de heap no exemplo 1.

Alocadores personalizados e estouro de contêiner

As verificações de estouro de contêiner do Address Sanitizer suportam nãostd::allocator alocadores. No entanto, como o AddressSanitizer não sabe se um alocador personalizado está em conformidade com os requisitos do AddressSanitizer, como alinhar alocações em limites de 8 bytes ou não colocar dados entre o final da alocação e o próximo limite de 8 bytes, ele nem sempre pode verificar se os acessos na última extremidade de uma alocação estão corretos.

O AddressSanitizer marca blocos de memória em blocos de 8 bytes. Ele não pode colocar bytes inacessíveis antes de bytes acessíveis em um único bloco. É válido ter 8 bytes acessíveis em um bloco, ou 4 bytes acessíveis seguidos por 4 bytes inacessíveis. Quatro bytes inacessíveis não podem ser seguidos por 4 bytes acessíveis.

Se o final de uma alocação de um alocador personalizado não se alinhar estritamente com o final de uma parte de 8 bytes, o AddressSanitizer deverá assumir que o alocador disponibiliza os bytes entre o final da alocação e o final da parte para o alocador ou o usuário gravar. Portanto, ele não pode marcar os bytes na parte final de 8 bytes como inacessíveis. No exemplo a seguir de um vector que aloca memória usando um alocador personalizado, '?' refere-se a dados não inicializados e '-' refere-se à memória inacessível.

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

No exemplo anterior, a parte 3 tem 4 bytes de memória que são considerados inacessíveis porque estão entre o final da alocação dos 20 bytes que foram reservados (v.reserve(20)) e o final do terceiro agrupamento lógico de 8 bytes (lembre-se de que AddressSanitizer marca blocos de memória em partes de 8 bytes).

Idealmente, marcaríamos a memória de sombra, que o Address Sanitizer reserva para cada bloco de memória de 8 bytes para rastrear quais bytes nesse bloco de 8 bytes são válidos e quais são inválidos (e por quê), de modo que v.data() + [0, v.size()) são acessíveis e v.data() + [v.size(), v.capacity()) são inacessíveis. Observe o uso da notação de intervalo aqui: '[' significa inclusivo de e ')' significa exclusivo de. Se o usuário estiver usando um alocador personalizado, não saberemos se a memória depois v.data() + v.capacity() está acessível ou não. Devemos supor que sim. Preferimos marcar esses bytes como inacessíveis na memória de sombra, mas devemos marcá-los como acessíveis para que seja possível acessá-los após a alocação.

std::allocator usa a _Minimum_asan_allocation_alignment variável de membro estático para informar vector e string que eles podem confiar no alocador para não colocar dados logo após a alocação. Isso garante que o alocador não usará a memória entre o final da alocação e o final da parte. Assim, essa parte da parte pode ser marcada como inacessível pelo Address Sanitizer para capturar excessos.

Se você quiser que a implementação confie que seu alocador personalizado está manipulando a memória entre o final da alocação e o final da parte para que ele possa marcar essa memória como inacessível e capturar excessos, defina _Minimum_asan_allocation_alignment como seu alinhamento mínimo real. Para que o AddressSanitizer funcione corretamente, o alinhamento deve ser de pelo menos 8.

Confira também

Visão geral do AddressSanitizer
Problemas conhecidos do AddressSanitizer
Referência de linguagem e build do AddressSanitizer
Referência de runtime do AddressSanitizer
Bytes de sombra de AddressSanitizer
Nuvem do AddressSanitizer ou teste distribuído
Integração do depurador do AddressSanitizer
Exemplos de erro do AddressSanitizer