Partage via


Erreur : container-overflow

Erreur d’assainissement de l’adresse : dépassement de capacité du conteneur

Dans Visual Studio 2022 version 17.2 et ultérieures, la bibliothèque standard Microsoft Visual C++ (STL) est partiellement éclairée pour fonctionner avec AddressSanitizer. Les types de conteneurs suivants ont des annotations pour détecter les problèmes d’accès à la mémoire :

Type de conteneur standard Désactiver la macro d’annotations Prise en charge dans la version
std::vector _DISABLE_VECTOR_ANNOTATION Visual Studio 2022 17.2
std::string _DISABLE_STRING_ANNOTATION Visual Studio 2022 17.6

Il existe des vérifications pour s’assurer qu’il n’existe aucune violation de règle de définition unique (ODR). Une violation ODR se produit lorsqu’une unité de traduction annote un type standard, tel que vector, avec des annotations ASan, mais pas une autre unité de traduction. Dans cet exemple, l’éditeur de liens peut voir simultanément une déclaration de vector<int>::push_back ce qui comporte des annotations d’assainissement d’adresse, et une autre déclaration de vector<int>::push_back cela ne le fait pas. Pour éviter ce problème, chaque bibliothèque statique et objet utilisé pour lier le fichier binaire doit également activer les annotations ASan. En fait, vous devez générer ces bibliothèques et objets statiques avec AddressSanitizer activé. Le mélange de code avec différents paramètres d’annotation provoque une erreur :

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

Pour résoudre cette erreur, désactivez les annotations dans tous les projets qui utilisent la macro correspondante, ou générez chaque projet à l’aide /fsanitize=address des annotations activées. (Les annotations sont activées par défaut.)

Exemple : Accéder à la mémoire réservée dans 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;
}

Pour générer et tester cet exemple, exécutez les commandes suivantes dans une fenêtre d’invite de commandes développeur visual Studio 2022 version 17.2 ou ultérieure :

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

Résultat d’erreur de l’accès à la mémoire réservée dans un std::vector

Capture d’écran du débogueur affichant l’erreur de dépassement de conteneur dans l’exemple 1.

Allocators personnalisés et dépassement de capacité de conteneur

Les contrôles de dépassement de capacité du conteneur d’assainissement de l’adresse prennent en charge les non-allocateursstd::allocator . Toutefois, étant donné que AddressSanitizer ne sait pas si un allocateur personnalisé est conforme aux exigences AddressSanitizer telles que l’alignement des allocations sur les limites de 8 octets, ou ne pas placer les données entre la fin de l’allocation et la limite de 8 octets suivante, il peut ne pas toujours être en mesure de vérifier que les accès à la dernière fin d’une allocation sont corrects.

AddressSanitizer marque les blocs de mémoire en blocs de 8 octets. Il ne peut pas placer d’octets inaccessibles avant les octets accessibles dans un bloc unique. Il est valide d’avoir 8 octets accessibles dans un bloc, ou 4 octets accessibles suivis de 4 octets inaccessibles. Quatre octets inaccessibles ne peuvent pas être suivis de 4 octets accessibles.

Si la fin d’une allocation à partir d’un allocateur personnalisé ne s’aligne pas strictement sur la fin d’un bloc de 8 octets, AddressSanitizer doit supposer que l’allocator effectue les octets entre la fin de l’allocation et la fin du bloc disponible pour l’allocator ou l’utilisateur à écrire. Par conséquent, il ne peut pas marquer les octets dans le bloc de 8 octets final comme inaccessible. Dans l’exemple suivant d’un vector allocateur personnalisé qui alloue de la mémoire, « ? » fait référence à des données non initialisées et « - » fait référence à la mémoire inaccessible.

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

Dans l’exemple précédent, le bloc 3 comporte 4 octets de mémoire qui sont supposés être inaccessibles, car ils se situent entre la fin de l’allocation des 20 octets réservés (v.reserve(20)) et la fin du troisième regroupement logique de 8 octets (n’oubliez pas que AddressSanitizer marque les blocs de mémoire en blocs de 8 octets).

Dans l’idéal, nous allons marquer la mémoire d’ombre, que address sanitizer met de côté pour chaque bloc de mémoire de 8 octets pour suivre les octets de ce bloc de 8 octets valides et non valides (et pourquoi), tels qu’ils v.data() + [0, v.size()) sont accessibles et v.data() + [v.size(), v.capacity()) inaccessibles. Notez ici l’utilisation de la notation d’intervalle : « [ » signifie inclus, et « ) » signifie exclusif. Si l’utilisateur utilise un allocateur personnalisé, nous ne savons pas si la mémoire après v.data() + v.capacity() est accessible ou non. Nous devons supposer qu’il s’agit. Nous préférons marquer ces octets comme inaccessibles dans la mémoire fantôme, mais nous devons les marquer comme accessibles afin qu’il reste possible d’accéder à ces octets après l’allocation.

std::allocator utilise la _Minimum_asan_allocation_alignment variable membre statique pour indiquer vector et string qu’elle peut approuver l’allocateur pour ne pas placer les données juste après l’allocation. Cela garantit que l’allocateur n’utilise pas la mémoire entre la fin de l’allocation et la fin du bloc. Ainsi, cette partie du bloc peut être marquée inaccessible par l’assainissant d’adresse pour intercepter les dépassements.

Si vous souhaitez que l’implémentation approuve que votre allocateur personnalisé gère la mémoire entre la fin de l’allocation et la fin du bloc afin qu’elle puisse marquer cette mémoire comme inaccessible et intercepter les dépassements, définissez _Minimum_asan_allocation_alignment votre alignement minimal réel. Pour que AddressSanitizer fonctionne correctement, l’alignement doit être au moins 8.

Voir aussi

Vue d’ensemble de AddressSanitizer
Résoudre les problèmes connus liés à AddressSanitizer
Référence de build et de langage AddressSanitizer
Informations de référence sur le runtime AddressSanitizer
Octets d’ombre AddressSanitizer
Test cloud ou distribué AddressSanitizer
Intégration du débogueur AddressSanitizer
Exemples d’erreur AddressSanitizer