Errore: container-overflow

Errore di purificazione dell'indirizzo: overflow del contenitore

In Visual Studio 2022 versione 17.2 e successive, la libreria standard di Microsoft Visual C++ è parzialmente usata con AddressSanitizer. I tipi di contenitore seguenti includono annotazioni per rilevare i problemi di accesso alla memoria:

Tipo di contenitore standard Disabilitare la macro delle annotazioni Supportato nella versione
std::vector _DISABLE_VECTOR_ANNOTATION Visual Studio 2022 17.2
std::string _DISABLE_STRING_ANNOTATION Visual Studio 2022 17.6

Sono disponibili controlli per assicurarsi che non siano presenti violazioni ODR (One-Definition-Rule). Una violazione ODR si verifica quando un'unità di conversione annota un tipo standard, ad esempio vector, con annotazioni ASan, ma un'altra unità di conversione non lo fa. In questo esempio, il linker potrebbe visualizzare contemporaneamente una dichiarazione di vector<int>::push_back con annotazioni di pulizia degli indirizzi e un'altra dichiarazione di vector<int>::push_back che non lo fa. Per evitare questo problema, ogni libreria statica e oggetto usato per collegare il file binario deve anche abilitare le annotazioni ASan. In effetti, è necessario compilare tali librerie statiche e oggetti con AddressSanitizer abilitato. La combinazione di codice con impostazioni di annotazione diverse causa un errore:

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

Per risolvere questo errore, disabilitare le annotazioni in tutti i progetti che usano la macro corrispondente oppure compilare ogni progetto usando /fsanitize=address e annotazioni abilitate. Le annotazioni sono abilitate per impostazione predefinita.

Esempio: Accedere alla memoria riservata in 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;
}

Per compilare e testare questo esempio, eseguire i comandi seguenti in una finestra del prompt dei comandi per sviluppatori di Visual Studio 2022 versione 17.2 o successiva:

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

Risultato dell'errore di accesso alla memoria riservata in un std::vector

Screenshot del debugger che mostra l'errore di overflow del contenitore nell'esempio 1.

Allocatori personalizzati e overflow dei contenitori

I controlli dell'overflow dei contenitori di Sanitizer supportano gli allocatori nonstd::allocator . Tuttavia, poiché AddressSanitizer non sa se un allocatore personalizzato è conforme ai requisiti AddressSanitizer, ad esempio l'allineamento delle allocazioni ai limiti a 8 byte o l'inserimento di dati tra la fine dell'allocazione e il limite a 8 byte successivo, potrebbe non essere sempre in grado di verificare che gli accessi alla fine di un'allocazione siano corretti.

AddressSanitizer contrassegna i blocchi di memoria in blocchi a 8 byte. Non può posizionare byte inaccessibili prima dei byte accessibili in un singolo blocco. È valido avere 8 byte accessibili in un blocco o 4 byte accessibili seguiti da 4 byte inaccessibili. Non è possibile seguire quattro byte inaccessibili da 4 byte accessibili.

Se la fine di un'allocazione da un allocatore personalizzato non è strettamente allineata alla fine di un blocco a 8 byte, AddressSanitizer deve presupporre che l'allocatore renda i byte tra la fine dell'allocazione e la fine del blocco disponibile per l'allocatore o l'utente in cui scrivere. Pertanto, non può contrassegnare i byte nel blocco finale a 8 byte come inaccessibile. Nell'esempio seguente di un oggetto vector che alloca memoria usando un allocatore personalizzato, '?' fa riferimento a dati non inizializzati e '-' fa riferimento alla memoria inaccessibile.

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

Nell'esempio precedente, il blocco 3 ha 4 byte di memoria che si presuppone non siano accessibili perché rientrano tra la fine dell'allocazione dei 20 byte riservati (v.reserve(20)) e la fine del terzo raggruppamento logico di 8 byte (tenere presente che AddressSanitizer contrassegna i blocchi di memoria in blocchi a 8 byte).

Idealmente, si contrassegna la memoria shadow, che Address Sanitizer mette da parte per ogni blocco di memoria a 8 byte per tenere traccia dei byte in quel blocco a 8 byte validi e quali non sono validi (e perché), tali che v.data() + [0, v.size()) sono accessibili e v.data() + [v.size(), v.capacity()) sono inaccessibili. Si noti l'uso della notazione di intervallo qui: '[' significa inclusivo di e ')' significa esclusivo di. Se l'utente usa un allocatore personalizzato, non sappiamo se la memoria dopo v.data() + v.capacity() è accessibile o meno. Dobbiamo presupporre che sia. È preferibile contrassegnare tali byte come inaccessibili nella memoria shadow, ma è necessario contrassegnarli come accessibili in modo che rimanga possibile accedere a tali byte dopo l'allocazione.

std::allocator usa la _Minimum_asan_allocation_alignment variabile membro statica per indicare vector e string che può considerare attendibile l'allocatore per non inserire i dati subito dopo l'allocazione. In questo modo, l'allocatore non userà la memoria tra la fine dell'allocazione e la fine del blocco. In questo modo, la parte del blocco può essere contrassegnata inaccessibile da Address Sanitizer per rilevare i sovraccarichi.

Se si vuole che l'implementazione consideri attendibile che l'allocatore personalizzato gestisca la memoria tra la fine dell'allocazione e la fine del blocco in modo che possa contrassegnare tale memoria come inaccessibile e intercettare overrun, impostare sull'allineamento _Minimum_asan_allocation_alignment minimo effettivo. Per il corretto funzionamento di AddressSanitizer, l'allineamento deve essere almeno 8.

Vedi anche

Panoramica di AddressSanitizer
Problemi noti di AddressSanitizer
Riferimento alla compilazione e al linguaggio AddressSanitizer
Informazioni di riferimento sul runtime AddressSanitizer
Byte ombreggiatura AddressSanitizer
AddressSanitizer cloud o test distribuiti
Integrazione del debugger AddressSanitizer
Esempi di errore addressSanitizer