Udostępnij za pośrednictwem


Błąd: container-overflow

Błąd narzędzia do oczyszczania adresu: przepełnienie kontenera

W programie Visual Studio 2022 w wersji 17.2 lub nowszej biblioteka standardowa Microsoft Visual C++ (STL) jest częściowo obsługiwana do pracy z narzędziem AddressSanitizer. Następujące typy kontenerów mają adnotacje do wykrywania problemów z dostępem do pamięci:

Standardowy typ kontenera Wyłączanie makra adnotacji Obsługiwane w wersji
std::vector _DISABLE_VECTOR_ANNOTATION Visual Studio 2022 17.2
std::string _DISABLE_STRING_ANNOTATION Visual Studio 2022 17.6

Istnieją kontrole, aby upewnić się, że nie ma żadnych naruszeń reguły definicji (ODR). Naruszenie odr występuje, gdy jedna jednostka tłumaczenia donotuje typ standardowy, taki jak vector, z adnotacjami ASan, ale inna jednostka tłumaczenia nie. W tym przykładzie konsolidator może jednocześnie zobaczyć jedną deklarację vector<int>::push_back , która zawiera adnotacje sanitizera adresów, a inna deklaracja vector<int>::push_back tego nie jest. Aby uniknąć tego problemu, każda biblioteka statyczna i obiekt używany do łączenia pliku binarnego muszą również włączyć adnotacje ASan. W rzeczywistości należy utworzyć te biblioteki i obiekty statyczne z włączonym modułem AddressSanitizer. Mieszanie kodu z różnymi ustawieniami adnotacji powoduje błąd:

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

Aby rozwiązać ten błąd, wyłącz adnotacje we wszystkich projektach korzystających z odpowiedniego makra lub skompiluj każdy projekt przy użyciu /fsanitize=address adnotacji i włączone adnotacje. (Adnotacje są domyślnie włączone).

Przykład: uzyskiwanie dostępu do pamięci zarezerwowanej w obiekcie 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;
}

Aby skompilować i przetestować ten przykład, uruchom następujące polecenia w oknie wiersza polecenia programu Visual Studio 2022 w wersji 17.2 lub nowszej:

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

Błąd wyniku dostępu do pamięci zarezerwowanej w obiekcie std::vector

Zrzut ekranu przedstawiający debuger wyświetlający błąd przepełnienia kontenera w przykładzie 1.

Niestandardowe alokatory i przepełnienie kontenera

Sprawdzanie przepełnienia kontenera Sanitizer obsługuje nieprzydzielnikistd::allocator . Jednak ze względu na to, że narzędzie AddressSanitizer nie wie, czy niestandardowy alokator jest zgodny z wymaganiami AddressSanitizer, takimi jak wyrównanie alokacji na granicach 8 bajtów lub nie umieszcza danych między końcem alokacji a następnym granicą 8 bajtów, może nie zawsze być w stanie sprawdzić, czy dostęp do ostatniego końca alokacji jest poprawny.

AddressSanitizer oznacza bloki pamięci we fragmentach 8-bajtowych. Nie może umieścić niedostępnych bajtów przed udostępnieniem bajtów w jednym kawałku. Ważne jest, aby mieć 8 dostępnych bajtów we fragmentach lub 4 dostępne bajty, po których następuje 4 bajty niedostępne. Po czterech niedostępnych bajtach nie można śledzić 4 bajtów dostępnych.

Jeśli koniec alokacji z alokatora niestandardowego nie jest ściśle zgodny z końcem fragmentu 8-bajtowego, addressSanitizer musi przyjąć, że alokator sprawia, że bajty między końcem alokacji a końcem fragmentu dostępnego dla alokatora lub użytkownika do zapisu. W związku z tym nie może oznaczyć bajtów w ostatnim 8-bajtowym fragmentie jako niedostępnym. W poniższym przykładzie obiektu vector , który przydziela pamięć przy użyciu niestandardowego alokatora, "?", odnosi się do niezainicjowanych danych i "-" odnosi się do pamięci, która jest niedostępna.

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

W poprzednim przykładzie fragment 3 ma 4 bajty pamięci, które są zakładane jako niedostępne, ponieważ znajdują się między końcem alokacji 20 bajtów zarezerwowanych (v.reserve(20)) i końcem trzeciej grupy logicznej 8 bajtów (pamiętaj, że funkcja AddressSanitizer oznacza bloki pamięci w 8-bajtowych fragmentach).

W idealnym przypadku oznaczylibyśmy pamięć w tle, którą narzędzie Address Sanitizer odkłada do każdego 8-bajtowego bloku pamięci, aby śledzić, które bajty w tym bloku 8 bajtów są prawidłowe i które są nieprawidłowe (i dlaczego), takie, które v.data() + [0, v.size()) są dostępne i v.data() + [v.size(), v.capacity()) są niedostępne. Należy zwrócić uwagę na użycie notacji interwału w tym miejscu: "[" oznacza inkluzywność i ")" oznacza wyłączność. Jeśli użytkownik korzysta z niestandardowego alokatora, nie wiemy, czy pamięć po v.data() + v.capacity() jest dostępna, czy nie. Musimy założyć, że tak jest. Wolelibyśmy oznaczyć te bajty jako niedostępne w pamięci w tle, ale musimy oznaczyć je jako dostępne, aby można było uzyskać dostęp do tych bajtów po alokacji.

std::allocator Używa statycznej zmiennej _Minimum_asan_allocation_alignment składowej, aby poinformować vector i string że mogą ufać alokatorowi, aby nie umieszczać danych bezpośrednio po alokacji. Gwarantuje to, że alokator nie będzie używać pamięci między końcem alokacji a końcem fragmentu. W związku z tym część fragmentu może być oznaczona jako niedostępna przez moduł sanitizer adresu, aby przechwycić przekroczenia.

Jeśli chcesz, aby implementacja ufała, że niestandardowy alokator obsługuje pamięć między końcem alokacji a końcem fragmentu, aby można było oznaczyć ją jako niedostępną i przechwytywać przekroczenia, ustaw _Minimum_asan_allocation_alignment na rzeczywiste wyrównanie minimalne. Aby polecenie AddressSanitizer działało poprawnie, wyrównanie musi być równe co najmniej 8.

Zobacz też

AddressSanitizer — omówienie
Rozwiązywanie znanych problemów z programemSanitizer
Dokumentacja języka i kompilacji narzędzia AddressSanitizer
AddressSanitizer runtime reference (Dokumentacja środowiska uruchomieniowego AddressSanitizer)
Bajty w tle addressSanitizer
AddressSanitizer — chmura lub testowanie rozproszone
Integracja debugera AddressSanitizer
Przykłady błędów addressSanitizer