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
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