GWP-ASan to funkcja alokatora pamięci natywnej, która pomaga znaleźć błędy use-after-free i heap-buffer-overflow. Jego nieoficjalna nazwa to akronim rekurencyjny „GWP-ASan Will Provide Allocation SANity”. W przeciwieństwie do HWASan i debugowania metodą malloc GWP-ASan nie wymaga kodu źródłowego ani rekompilacji (czyli działa z gotowymi kompilacjami) i działa zarówno w procesach 32-, jak i 64-bitowych (chociaż awarie 32-bitowe zawierają mniej informacji na potrzeby debugowania). W tym temacie opisano działania, które należy wykonać, aby włączyć tę funkcję w aplikacji. GWP-ASan jest dostępny w aplikacjach kierowanych na Androida 11 (API na poziomie 30) lub nowszego.
Przegląd
GWP-ASan jest włączany w niektórych losowo wybranych aplikacjach systemowych i plikach wykonywalnych platformy podczas uruchamiania procesu (lub gdy zygote tworzy proces potomny). Włącz GWP-ASan we własnej aplikacji, aby ułatwić sobie znajdowanie błędów związanych z pamięcią i przygotować aplikację do obsługi ARM Memory Tagging Extension (MTE). Mechanizmy próbkowania alokacji zapewniają też niezawodność w przypadku zapytań powodujących awarię.
Po włączeniu GWP-ASan przechwytuje losowo wybrany podzbiór alokacji sterty i umieszcza je w specjalnym regionie, który wykrywa trudne do wykrycia błędy uszkodzenia pamięci sterty. Przy wystarczającej liczbie użytkowników nawet ta niska częstotliwość próbkowania pozwoli wykryć błędy bezpieczeństwa pamięci sterty, które nie są wykrywane podczas regularnych testów. Na przykład GWP-ASan wykrył znaczną liczbę błędów w przeglądarce Chrome (wiele z nich jest nadal w widoku z ograniczonym dostępem).
GWP-ASan zbiera dodatkowe informacje o wszystkich przechwytywanych alokacjach. Te informacje są dostępne, gdy GWP-ASan wykryje naruszenie bezpieczeństwa pamięci, i są automatycznie umieszczane w raporcie o awarii systemowej, co może znacznie ułatwić debugowanie (patrz Przykład).
GWP-ASan nie powoduje znacznego obciążenia procesora. Po włączeniu GWP-ASan powoduje niewielkie, stałe obciążenie pamięci RAM. Ten narzut jest określany przez system Android i obecnie wynosi około 70 kibibajtów (KiB) na każdy proces, którego dotyczy.
Wyrażanie zgody na korzystanie z aplikacji
GWP-ASan może być włączany przez aplikacje na poziomie procesu za pomocą tagu android:gwpAsanMode w manifeście aplikacji. Obsługiwane są te opcje:
Zawsze wyłączone (
android:gwpAsanMode="never"): to ustawienie całkowicie wyłącza GWP-ASan w aplikacji i jest domyślne w przypadku aplikacji niesystemowych.Domyślne (
android:gwpAsanMode="default"lub nieokreślone): Android 13 (API na poziomie 33) i starsze – GWP-ASan jest wyłączony. Android 14 (poziom 34 interfejsu API) i nowsze – włączona jest funkcja Recoverable GWP-ASan.Zawsze włączone (
android:gwpAsanMode="always"): to ustawienie włącza GWP-ASan w aplikacji, co obejmuje:System operacyjny rezerwuje stałą ilość pamięci RAM na operacje GWP-ASan, czyli około 70 KiB na każdy proces, którego to dotyczy. (Włącz GWP-ASan, jeśli Twoja aplikacja nie jest bardzo wrażliwa na wzrost wykorzystania pamięci).
GWP-ASan przechwytuje losowo wybrany podzbiór alokacji sterty i umieszcza je w specjalnym regionie, który niezawodnie wykrywa naruszenia bezpieczeństwa pamięci.
Gdy w specjalnym regionie wystąpi naruszenie bezpieczeństwa pamięci, GWP-ASan zakończy proces.
GWP-ASan podaje dodatkowe informacje o błędzie w raporcie o awarii.
Aby włączyć GWP-ASan globalnie w aplikacji, dodaj do pliku AndroidManifest.xml ten kod:
<application android:gwpAsanMode="always"> ... </application>
Dodatkowo GWP-ASan można jawnie włączyć lub wyłączyć w przypadku określonych podprocesów aplikacji. Możesz kierować aktywności i usługi za pomocą procesów, które są jawnie włączone lub wyłączone w GWP-ASan. Oto przykład:
<application> <processes> <!-- Create the (empty) application process --> <process /> <!-- Create subprocesses with GWP-ASan both explicitly enabled and disabled. --> <process android:process=":gwp_asan_enabled" android:gwpAsanMode="always" /> <process android:process=":gwp_asan_disabled" android:gwpAsanMode="never" /> </processes> <!-- Target services and activities to be run on either the GWP-ASan enabled or disabled processes. --> <activity android:name="android.gwpasan.GwpAsanEnabledActivity" android:process=":gwp_asan_enabled" /> <activity android:name="android.gwpasan.GwpAsanDisabledActivity" android:process=":gwp_asan_disabled" /> <service android:name="android.gwpasan.GwpAsanEnabledService" android:process=":gwp_asan_enabled" /> <service android:name="android.gwpasan.GwpAsanDisabledService" android:process=":gwp_asan_disabled" /> </application>
Możliwy do odzyskania błąd GWP-ASan
Android 14 (poziom interfejsu API 34) i nowsze obsługują Recoverable GWP-ASan, który pomaga deweloperom znajdować błędy przepełnienia bufora na stercie i odwołania do pamięci po jej zwolnieniu w środowisku produkcyjnym bez pogarszania wrażeń użytkowników. Jeśli w AndroidManifest.xml nie określono android:gwpAsanMode, aplikacja używa GWP-ASan z możliwością odzyskania.
Naprawialny GWP-ASan różni się od podstawowego GWP-ASan pod tymi względami:
- Funkcja Recoverable GWP-ASan jest włączana tylko w około 1% przypadków uruchomienia aplikacji, a nie przy każdym uruchomieniu.
- W przypadku wykrycia błędu heap-use-after-free lub heap-buffer-overflow pojawia się on w raporcie o awarii (tombstone). Ten raport o awariach jest dostępny za pomocą interfejsu API
ActivityManager#getHistoricalProcessExitReasons, tak samo jak oryginalny GWP-ASan. - Zamiast kończyć działanie po zrzuceniu raportu o awarii, Recoverable GWP-ASan pozwala na uszkodzenie pamięci, a aplikacja nadal działa. Proces może być kontynuowany jak zwykle, ale zachowanie aplikacji nie jest już określone. Z powodu uszkodzenia pamięci aplikacja może w przyszłości ulec awarii w dowolnym momencie lub może nadal działać bez żadnego widocznego dla użytkownika wpływu.
- Po zrzucie raportu o awarii funkcja Recoverable GWP-ASan jest wyłączana. Dlatego aplikacja może otrzymać tylko 1 raport GWP-ASan dotyczący możliwych do naprawienia błędów na każde uruchomienie.
- Jeśli w aplikacji jest zainstalowany niestandardowy moduł obsługi sygnałów, nigdy nie jest on wywoływany w przypadku sygnału SIGSEGV, który wskazuje na błąd GWP-ASan, który można naprawić.
Awarie wykrywane przez Recoverable GWP-ASan wskazują na rzeczywiste przypadki uszkodzenia pamięci na urządzeniach użytkowników, dlatego zdecydowanie zalecamy, aby błędy wykrywane przez to narzędzie traktować priorytetowo i jak najszybciej je naprawiać.
Pomoc dla programistów
W tych sekcjach opisano problemy, które mogą wystąpić podczas korzystania z GWP-ASan, oraz sposoby ich rozwiązywania.
Brak śladów alokacji i dealokacji
Jeśli diagnozujesz awarię systemową, w której wydaje się brakować ramek alokacji lub cofnięcia alokacji, w aplikacji prawdopodobnie brakuje wskaźników ramek. GWP-ASan używa wskaźników ramek do rejestrowania śladów alokacji i dezalokacji ze względu na wydajność i nie może rozwinąć zrzutu stosu, jeśli ich nie ma.
Wskaźniki ramek są domyślnie włączone na urządzeniach z procesorem arm64 i wyłączone na urządzeniach z procesorem arm32. Ponieważ aplikacje nie mają kontroli nad libc, GWP-ASan (ogólnie) nie może zbierać śladów alokacji/dezalokacji w przypadku 32-bitowych plików wykonywalnych ani aplikacji. Aplikacje 64-bitowe nie powinny być kompilowane z użyciem -fomit-frame-pointer, aby GWP-ASan mógł zbierać zrzuty stosu alokacji i dezalokacji.
Odtwarzanie naruszeń zasad bezpieczeństwa
GWP-ASan ma na celu wykrywanie naruszeń bezpieczeństwa pamięci sterty na urządzeniach użytkowników. GWP-ASan podaje jak najwięcej informacji kontekstowych o awarii (log czasu dostępu do naruszenia, ciąg przyczyny oraz logi czasu przydzielenia i wycofania przydzielenia), ale nadal może być trudno ustalić, jak doszło do naruszenia. Niestety, ponieważ wykrywanie błędów jest probabilistyczne, raporty GWP-ASan są często trudne do odtworzenia na urządzeniu lokalnym.
Jeśli błąd dotyczy urządzeń 64-bitowych, użyj HWAddressSanitizer (HWASan). HWASan niezawodnie wykrywa naruszenia bezpieczeństwa pamięci na stosie, stercie i w zmiennych globalnych. Uruchomienie aplikacji z HWASan może niezawodnie odtworzyć ten sam wynik, który jest zgłaszany przez GWP-ASan.
Jeśli uruchomienie aplikacji w HWASan nie wystarczy do znalezienia głównej przyczyny błędu, spróbuj przeprowadzić fuzzing kodu. Możesz ukierunkować testy rozmyte na podstawie informacji z raportu GWP-ASan, który może niezawodnie wykrywać i ujawniać problemy z kodem.
Przykład
Ten przykładowy kod natywny zawiera błąd związany z użyciem pamięci sterty po jej zwolnieniu:
#include <jni.h>
#include <string>
#include <string_view>
jstring native_get_string(JNIEnv* env) {
std::string s = "Hellooooooooooooooo ";
std::string_view sv = s + "World\n";
// BUG: Use-after-free. `sv` holds a dangling reference to the ephemeral
// string created by `s + "World\n"`. Accessing the data here is a
// use-after-free.
return env->NewStringUTF(sv.data());
}
extern "C" JNIEXPORT jstring JNICALL
Java_android11_test_gwpasan_MainActivity_nativeGetString(
JNIEnv* env, jobject /* this */) {
// Repeat the buggy code a few thousand times. GWP-ASan has a small chance
// of detecting the use-after-free every time it happens. A single user who
// triggers the use-after-free thousands of times will catch the bug once.
// Alternatively, if a few thousand users each trigger the bug a single time,
// you'll also get one report (this is the assumed model).
jstring return_string;
for (unsigned i = 0; i < 0x10000; ++i) {
return_string = native_get_string(env);
}
return reinterpret_cast<jstring>(env->NewGlobalRef(return_string));
}
W przypadku testu z użyciem przykładowego kodu powyżej GWP-ASan wykrył nieprawidłowe użycie i wygenerował poniższy raport o awarii. GWP-ASan automatycznie ulepszył raport, podając informacje o rodzaju awarii, metadanych alokacji oraz powiązanych zrzutach stosu alokacji i dealokacji.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sargo/sargo:10/RPP3.200320.009/6360804:userdebug/dev-keys'
Revision: 'PVT1.0'
ABI: 'arm64'
Timestamp: 2020-04-06 18:27:08-0700
pid: 16227, tid: 16227, name: 11.test.gwpasan >>> android11.test.gwpasan <<<
uid: 10238
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x736ad4afe0
Cause: [GWP-ASan]: Use After Free on a 32-byte allocation at 0x736ad4afe0
backtrace:
#00 pc 000000000037a090 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckNonHeapValue(char, art::(anonymous namespace)::JniValueType)+448)
#01 pc 0000000000378440 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::(anonymous namespace)::JniValueType)+204)
#02 pc 0000000000377bec /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*)+612)
#03 pc 000000000036dcf4 /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*)+708)
#04 pc 000000000000eda4 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (_JNIEnv::NewStringUTF(char const*)+40)
#05 pc 000000000000eab8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+144)
#06 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
...
deallocated by thread 16227:
#00 pc 0000000000048970 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
#01 pc 0000000000048f30 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::deallocate(void*)+184)
#02 pc 000000000000f130 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::_DeallocateCaller::__do_call(void*)+20)
...
#08 pc 000000000000ed6c /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::~basic_string()+100)
#09 pc 000000000000ea90 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+104)
#10 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
...
allocated by thread 16227:
#00 pc 0000000000048970 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
#01 pc 0000000000048e4c /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::allocate(unsigned long)+368)
#02 pc 000000000003b258 /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan_malloc(unsigned long)+132)
#03 pc 000000000003bbec /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
#04 pc 0000000000010414 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (operator new(unsigned long)+24)
...
#10 pc 000000000000ea6c /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+68)
#11 pc 000000000000edf8 /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
...
Więcej informacji
Więcej informacji o implementacji GWP-ASan znajdziesz w dokumentacji LLVM. Więcej informacji o raportach o awariach systemowych na Androidzie znajdziesz w artykule Diagnozowanie awarii systemowych.