GWP-ASan ist eine Funktion für die native Speicherzuweisung, mit der sich Use-After-Free- und Heap-Buffer-Overflow-Fehler finden lassen. Der inoffizielle Name ist ein rekursives Akronym: „GWP-ASan Will Provide Allocation SANity“. Im Gegensatz zu HWASan oder Malloc Debug ist für GWP-ASan kein Quellcode oder eine Neukompilierung erforderlich. Es funktioniert also mit Prebuilts und sowohl mit 32- als auch mit 64-Bit-Prozessen. Bei 32-Bit-Abstürzen sind jedoch weniger Debugging-Informationen verfügbar. In diesem Thema wird beschrieben, welche Maßnahmen Sie ergreifen müssen, um diese Funktion in Ihrer App zu aktivieren. GWP-ASan ist für Apps verfügbar, die auf Android 11 (API-Level 30) oder höher ausgerichtet sind.
Übersicht
GWP-ASan wird beim Start des Prozesses (oder wenn der Zygote-Prozess verzweigt wird) für einige zufällig ausgewählte Systemanwendungen und ausführbare Dateien der Plattform aktiviert. Aktivieren Sie GWP-ASan in Ihrer eigenen App, um speicherbezogene Fehler zu finden und Ihre App auf die Unterstützung der ARM Memory Tagging Extension (MTE) vorzubereiten. Die Sampling-Mechanismen für die Zuweisung bieten auch Zuverlässigkeit bei Anfragen zum Tod.
Wenn GWP-ASan aktiviert ist, werden zufällig ausgewählte Heap-Zuweisungen abgefangen und in einem speziellen Bereich platziert, in dem schwer zu erkennende Heap-Speicherfehler erkannt werden können. Bei einer ausreichenden Anzahl von Nutzern werden auch bei dieser niedrigen Samplingrate Heap-Speichersicherheitsfehler gefunden, die bei regulären Tests nicht gefunden werden. GWP-ASan hat beispielsweise eine beträchtliche Anzahl von Fehlern im Chrome-Browser gefunden, von denen viele noch nicht öffentlich sind.
GWP-ASan erfasst zusätzliche Informationen zu allen Zuweisungen, die es abfängt. Diese Informationen sind verfügbar, wenn GWP‑ASan einen Verstoß gegen die Speichersicherheit erkennt. Sie werden automatisch in den nativen Absturzbericht aufgenommen, was das Debugging erheblich erleichtern kann (siehe Beispiel).
GWP-ASan ist so konzipiert, dass es keinen erheblichen CPU-Overhead verursacht. GWP-ASan führt bei Aktivierung zu einem kleinen, festen RAM-Overhead. Dieser Overhead wird vom Android-System festgelegt und beträgt derzeit etwa 70 Kibibyte (KiB) für jeden betroffenen Prozess.
App anmelden
GWP-ASan kann von Apps auf Prozessebene mithilfe des android:gwpAsanMode
-Tags im App-Manifest aktiviert werden. Die folgenden Optionen werden unterstützt:
Immer deaktiviert (
android:gwpAsanMode="never"
): Diese Einstellung deaktiviert GWP-ASan in Ihrer App vollständig. Sie ist die Standardeinstellung für Nicht-System-Apps.Standard (
android:gwpAsanMode="default"
oder nicht angegeben): Android 13 (API‑Level 33) und niedriger – GWP-ASan ist deaktiviert. Android 14 (API‑Level 34) und höher: Wiederherstellbares GWP‑ASan ist aktiviert.Immer aktiviert (
android:gwpAsanMode="always"
): Mit dieser Einstellung wird GWP-ASan in Ihrer App aktiviert. Das umfasst Folgendes:Das Betriebssystem reserviert eine feste Menge an RAM für GWP-ASan-Vorgänge, etwa 70 KiB für jeden betroffenen Prozess. Aktivieren Sie GWP-ASan, wenn Ihre App nicht kritisch auf eine Erhöhung der Speichernutzung reagiert.
GWP-ASan fängt eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einem speziellen Bereich, in dem Speichersicherheitsverletzungen zuverlässig erkannt werden.
Wenn im speziellen Bereich ein Verstoß gegen die Speichersicherheit auftritt, beendet GWP-ASan den Prozess.
GWP‑ASan liefert zusätzliche Informationen zum Fehler im Absturzbericht.
Wenn Sie GWP-ASan global für Ihre App aktivieren möchten, fügen Sie der Datei AndroidManifest.xml
Folgendes hinzu:
<application android:gwpAsanMode="always"> ... </application>
Außerdem kann GWP-ASan explizit für bestimmte Unterprozesse Ihrer App aktiviert oder deaktiviert werden. Sie können Aktivitäten und Dienste mit Prozessen ausrichten, für die GWP-ASan explizit aktiviert oder deaktiviert wurde. Hier ein Beispiel:
<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>
Wiederherstellbarer GWP-ASan-Fehler
Android 14 (API‑Level 34) und höher unterstützen Recoverable GWP‑ASan. Damit können Entwickler Heap‑Buffer‑Overflow- und Heap‑Use‑After‑Free-Fehler in der Produktion finden, ohne die Nutzerfreundlichkeit zu beeinträchtigen. Wenn android:gwpAsanMode
in einem AndroidManifest.xml
nicht angegeben ist, verwendet die App Recoverable GWP-ASan.
Recoverable GWP-ASan unterscheidet sich in folgenden Punkten von GWP-ASan:
- Die Funktion „Wiederherstellbares GWP-ASan“ ist nur bei etwa 1% der App-Starts aktiviert, nicht bei jedem App-Start.
- Wenn ein Fehler vom Typ „Heap-Use-After-Free“ oder „Heap-Buffer-Overflow“ erkannt wird, wird dieser Fehler im Absturzbericht (Tombstone) angezeigt. Dieser Absturzbericht ist über die
ActivityManager#getHistoricalProcessExitReasons
API verfügbar, genau wie der ursprüngliche GWP-ASan. - Anstatt nach dem Speichern des Absturzberichts zu beenden, ermöglicht Recoverable GWP-ASan, dass Speicherschäden auftreten, und die App wird weiter ausgeführt. Der Prozess kann wie gewohnt fortgesetzt werden, das Verhalten der App wird jedoch nicht mehr angegeben. Aufgrund der Speicherbeschädigung kann die App in Zukunft an einem beliebigen Punkt abstürzen oder ohne für den Nutzer sichtbare Auswirkungen weiterlaufen.
- Die wiederherstellbare GWP-ASan-Funktion wird deaktiviert, nachdem der Absturzbericht gespeichert wurde. Daher kann eine App pro App-Start nur einen einzigen wiederherstellbaren GWP-ASan-Bericht erhalten.
- Wenn in der App ein benutzerdefinierter Signal-Handler installiert ist, wird er nie für ein SIGSEGV-Signal aufgerufen, das auf einen behebaren GWP-ASan-Fehler hinweist.
Da wiederherstellbare GWP-ASan-Abstürze auf tatsächliche Fälle von Speicherschäden auf Endnutzergeräten hinweisen, empfehlen wir dringend, von wiederherstellbarem GWP-ASan identifizierte Fehler mit hoher Priorität zu beheben.
Entwicklersupport
In diesen Abschnitten werden Probleme beschrieben, die bei der Verwendung von GWP-ASan auftreten können, und Sie erfahren, wie Sie diese beheben können.
Traces für die Zuweisung/Freigabe fehlen
Wenn Sie einen nativen Absturz diagnostizieren, bei dem Zuweisungs-/Freigabe-Frames fehlen, fehlen in Ihrer Anwendung wahrscheinlich Frame-Pointer. GWP-ASan verwendet Frame-Pointer, um aus Leistungsgründen Zuweisungs- und Freigabe-Traces aufzuzeichnen. Wenn keine Frame-Pointer vorhanden sind, kann der Stacktrace nicht zurückverfolgt werden.
Frame-Pointer sind für arm64-Geräte standardmäßig aktiviert und für arm32-Geräte standardmäßig deaktiviert. Da Anwendungen keine Kontrolle über libc haben, ist es (im Allgemeinen) nicht möglich, dass GWP-ASan Zuweisungs-/Freigabe-Traces für 32-Bit-Ausführungsdateien oder ‑Apps erfasst. Bei 64‑Bit-Anwendungen muss darauf geachtet werden, dass sie nicht mit -fomit-frame-pointer
erstellt werden, damit GWP‑ASan Stacktraces für die Zuordnung und Freigabe erfassen kann.
Sicherheitsverstöße reproduzieren
GWP-ASan wurde entwickelt, um Verstöße gegen die Heapspeicher-Sicherheit auf Nutzergeräten zu erkennen. GWP-ASan liefert so viele Informationen wie möglich zum Absturz (Zugriffspfad des Verstoßes, Ursachenstring und Zuweisungs-/Freigabepfade), aber es kann trotzdem schwierig sein, herauszufinden, wie der Verstoß aufgetreten ist. Da die Fehlererkennung probabilistisch ist, sind GWP-ASan-Berichte oft schwer auf einem lokalen Gerät zu reproduzieren.
Wenn der Fehler 64‑Bit-Geräte betrifft, sollten Sie HWAddressSanitizer (HWASan) verwenden. HWASan erkennt zuverlässig Verstöße gegen die Speichersicherheit in Stack, Heap und globalen Variablen. Wenn Sie Ihre Anwendung mit HWASan ausführen, kann das Ergebnis, das von GWP-ASan gemeldet wird, zuverlässig reproduziert werden.
Wenn das Ausführen Ihrer Anwendung unter HWASan nicht ausreicht, um die Ursache eines Fehlers zu ermitteln, sollten Sie versuchen, den betreffenden Code zu fuzzing. Sie können Ihre Fuzzing-Bemühungen auf Informationen im GWP-ASan-Bericht abstimmen, mit dem zugrunde liegende Probleme mit der Codeintegrität zuverlässig erkannt und aufgedeckt werden können.
Beispiel
Dieser Beispielcode enthält einen Use-after-Free-Fehler im Heap:
#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));
}
Bei einem Testlauf mit dem obigen Beispielcode hat GWP-ASan die unzulässige Verwendung erfolgreich erkannt und den unten stehenden Absturzbericht ausgelöst. GWP-ASan hat den Bericht automatisch verbessert, indem Informationen zum Absturztyp, zu den Zuweisungsmetadaten und zu den zugehörigen Stack-Traces für die Zuweisung und die Freigabe hinzugefügt wurden.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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)
...
Weitere Informationen
Weitere Informationen zu den Implementierungsdetails von GWP-ASan finden Sie in der LLVM-Dokumentation. Weitere Informationen zu nativen Absturzberichten unter Android finden Sie unter Native Abstürze diagnostizieren.