GWP-ASan ist eine Funktion zur nativen Arbeitsspeicherzuweisung, mit der sich Use-After-Free- und Heap-Buffer-Overflow-Fehler finden lassen. Der informelle Name ist ein rekursives Akronym: „GWP-ASan Will Provide Allocation SANity“. Im Gegensatz zu HWASan oder Malloc Debug erfordert GWP-ASan keine Quelle oder Neukompilierung (funktioniert also mit Prebuilts) und funktioniert sowohl bei 32- als auch bei 64-Bit-Prozessen (obwohl 32-Bit-Abstürze weniger Debugging-Informationen enthalten). In diesem Thema werden die Schritte beschrieben, die Sie ausführen 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 Starten des Prozesses (oder beim Forken der Zygote) 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 Mechanismen für die Zuweisungsstichprobenerhebung bieten auch Schutz vor Queries of Death.
Wenn GWP-ASan aktiviert ist, fängt es eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einem speziellen Bereich, in dem schwer zu erkennende Fehler im Zusammenhang mit der Beschädigung des Heap-Speichers abgefangen werden. Bei einer ausreichenden Anzahl von Nutzern werden selbst mit dieser niedrigen Abtastrate Heap-Speichersicherheitsfehler gefunden, die bei regulären Tests nicht gefunden werden. GWP-ASan hat beispielsweise eine erhebliche Anzahl von Fehlern im Chrome-Browser gefunden, von denen viele noch nicht öffentlich einsehbar sind.
GWP-ASan erfasst zusätzliche Informationen zu allen abgefangenen Zuweisungen. Diese Informationen sind verfügbar, wenn GWP-ASan eine Verletzung der Speichersicherheit erkennt. Sie werden automatisch in den nativen Absturzbericht aufgenommen, was die Fehlerbehebung erheblich erleichtern kann (siehe Beispiel).
GWP-ASan ist so konzipiert, dass kein nennenswerter CPU-Overhead entsteht. Wenn GWP-ASan aktiviert ist, entsteht ein geringer, fester RAM-Overhead. Dieser Overhead wird vom Android-System festgelegt und beträgt derzeit etwa 70 Kibibyte (KiB) pro betroffenen Prozess.
App aktivieren
GWP-ASan kann von Apps auf Prozessebene mithilfe des Tags android:gwpAsanMode im App-Manifest aktiviert werden. Es werden die folgenden Optionen unterstützt:
Immer deaktiviert (
android:gwpAsanMode="never"): Mit dieser Einstellung wird GWP-ASan in Ihrer App vollständig deaktiviert. Dies 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öhte Arbeitsspeichernutzung reagiert.)
GWP-ASan fängt eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einer speziellen Region, in der Verstöße gegen die Speichersicherheit zuverlässig erkannt werden.
Wenn in der speziellen Region 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 für bestimmte Unterprozesse Ihrer App explizit aktiviert oder deaktiviert werden. Sie können Aktivitäten und Dienste mit Prozessen ausrichten, die explizit für GWP-ASan aktiviert oder deaktiviert sind. Im Folgenden finden Sie 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>
Wiederherstellbare 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 einer AndroidManifest.xml nicht angegeben ist, verwendet die App den wiederherstellbaren GWP-ASan-Modus.
Das wiederherstellbare GWP-ASan unterscheidet sich vom Basis-GWP-ASan in folgenden Punkten:
- Der wiederherstellbare GWP-ASan-Modus wird nur bei etwa 1% der App-Starts aktiviert, nicht bei jedem App-Start.
- Wenn ein Heap-Use-After-Free- oder Heap-Buffer-Overflow-Fehler erkannt wird, wird dieser im Absturzbericht (Tombstone) angezeigt. Dieser Absturzbericht ist über die
ActivityManager#getHistoricalProcessExitReasonsAPI verfügbar, genau wie der ursprüngliche GWP-ASan-Bericht. - Anstatt nach dem Erstellen des Absturzberichts beendet zu werden, lässt Recoverable GWP-ASan Speicherschäden zu und die App wird weiter ausgeführt. Der Prozess kann zwar wie gewohnt fortgesetzt werden, das Verhalten der App ist jedoch nicht mehr festgelegt. Aufgrund der Speicherbeschädigung kann die App irgendwann in der Zukunft abstürzen oder ohne für den Nutzer sichtbare Auswirkungen weiterlaufen.
- Wiederherstellbares GWP-ASan wird deaktiviert, nachdem der Absturzbericht erstellt wurde. Daher kann eine App pro App-Start nur einen einzigen GWP-ASan-Bericht vom Typ „Wiederherstellbar“ erhalten.
- Wenn in der App ein benutzerdefinierter Signal-Handler installiert ist, wird er nie für ein SIGSEGV-Signal aufgerufen, das auf einen behebbaren GWP-ASan-Fehler hinweist.
Da wiederherstellbare GWP-ASan-Abstürze auf tatsächliche Fälle von Speicherbeschädigung auf Endnutzergeräten hinweisen, empfehlen wir dringend, die von wiederherstellbarem GWP-ASan identifizierten Fehler mit hoher Priorität zu sichten und zu beheben.
Support für Entwickler
In diesen Abschnitten werden Probleme beschrieben, die bei der Verwendung von GWP-ASan auftreten können, und wie Sie sie beheben können.
Traces zur Zuweisung/Aufhebung der Zuweisung fehlen
Wenn Sie einen nativen Absturz diagnostizieren, bei dem anscheinend Zuweisungs-/Freigabe-Frames fehlen, fehlen in Ihrer Anwendung wahrscheinlich Frame-Zeiger. GWP-ASan verwendet aus Leistungsgründen Frame-Pointer, um Zuweisungs- und Freigabe-Traces aufzuzeichnen. Wenn diese nicht vorhanden sind, kann der Stack-Trace nicht aufgelöst werden.
Frame-Zeiger sind auf arm64-Geräten standardmäßig aktiviert und auf arm32-Geräten standardmäßig deaktiviert. Da Anwendungen keine Kontrolle über libc haben, ist es für GWP-ASan im Allgemeinen nicht möglich, Zuordnungs-/Freigabe-Traces für ausführbare 32-Bit-Dateien oder ‑Apps zu erfassen. 64-Bit-Anwendungen sollten nicht mit -fomit-frame-pointer erstellt werden, damit GWP-ASan Stacktraces für die Zuweisung und Freigabe erfassen kann.
Sicherheitsverstöße reproduzieren
GWP-ASan wurde entwickelt, um Verstöße gegen die Speichersicherheit im Heap-Speicher auf Nutzergeräten zu erkennen. GWP-ASan liefert so viele Informationen wie möglich zum Absturz (Zugriffs-Trace des Verstoßes, Ursachenstring und Zuordnungs-/Aufhebungs-Traces). Es kann jedoch trotzdem schwierig sein, die Ursache des Verstoßes zu ermitteln. Da die Fehlererkennung probabilistisch ist, lassen sich GWP-ASan-Berichte auf einem lokalen Gerät oft nur schwer reproduzieren.
Wenn der Fehler 64-Bit-Geräte betrifft, sollten Sie in diesen Fällen HWAddressSanitizer (HWASan) verwenden. HWASan erkennt zuverlässig Verstöße gegen die Speichersicherheit im Stack, Heap und in globalen Variablen. Wenn Sie Ihre Anwendung mit HWASan ausführen, wird möglicherweise zuverlässig dasselbe Ergebnis reproduziert, das von GWP-ASan gemeldet wird.
Wenn das Ausführen Ihrer Anwendung unter HWASan nicht ausreicht, um die Ursache eines Fehlers zu ermitteln, sollten Sie versuchen, den betreffenden Code zu fuzzen. Sie können Ihre Fuzzing-Bemühungen auf Grundlage der Informationen im GWP-ASan-Bericht gezielt ausrichten. Dieser Bericht kann zugrunde liegende Probleme mit der Codeintegrität zuverlässig erkennen und aufdecken.
Beispiel
Dieser native Beispielcode enthält einen Heap-Use-after-free-Fehler:
#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 Nutzung erfolgreich erkannt und den folgenden Absturzbericht ausgelöst. GWP-ASan hat den Bericht automatisch um Informationen zum Absturztyp, zu den Zuordnungsmetadaten und zu den zugehörigen Stacktraces für die Zuordnung und Freigabe erweitert.
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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 für Android finden Sie unter Native Abstürze diagnostizieren.