GWP-ASan ist eine Funktion zur nativen 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 keine Neukompilierung erforderlich. Es funktioniert also mit vorkompilierten Binärdateien 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 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 Start des Prozesses (oder wenn der Zygote-Prozess verzweigt wird) für einige zufällig ausgewählte Systemanwendungen und Plattform Executables 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 zur Stichprobenerhebung bei der Zuweisung bieten auch Zuverlässigkeit bei Abfragen von Abstürzen.
Nach der Aktivierung fängt GWP-ASan eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einem speziellen Bereich, in dem schwer zu erkennende Heap- Speicherschäden erfasst werden. Bei einer ausreichenden Anzahl von Nutzern können mit dieser niedrigen Stichprobenrate Heap-Speichersicherheitsfehler gefunden werden, die bei regulären Tests nicht erkannt werden. GWP-ASan hat beispielsweise eine erhebliche Anzahl von Fehlern im Chrome-Browser gefunden, von denen viele noch nicht öffentlich sichtbar sind.
GWP-ASan erfasst zusätzliche Informationen zu allen abgefangenen 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 eingefügt, was das Debuggen erheblich erleichtern kann (siehe Beispiel).
GWP-ASan ist so konzipiert, dass keine erhebliche CPU-Auslastung 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 betroffenem Prozess.
App anmelden
GWP-ASan kann für Apps auf Prozessebene mit dem
android:gwpAsanMode Tag im App-Manifest aktiviert werden. Die folgenden Optionen werden
unterstützt:
Immer deaktiviert (
android:gwpAsanMode="never"): Mit dieser Einstellung wird GWP-ASan in Ihrer App vollständig deaktiviert. 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öhte 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 Verstöße gegen die Speichersicherheit zuverlässig erkannt werden.
Wenn im speziellen Bereich ein Verstoß gegen die Speichersicherheit auftritt, beendet GWP-ASan den Prozess.
GWP-ASan liefert im Absturz bericht zusätzliche Informationen zum Fehler.
Wenn Sie GWP-ASan global für Ihre App aktivieren möchten, fügen Sie der
AndroidManifest.xml Datei 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, die explizit für GWP-ASan angemeldet oder abgemeldet wurden. Ein Beispiel finden Sie unten:
<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>
Wiederherstellbares GWP-ASan
Android 14 (API-Level 34) und höher unterstützt wiederherstellbares GWP-ASan, mit dem Entwickler Heap-Buffer-Overflow- und Heap-Use-After-Free-Fehler in
der Produktion finden können, ohne die Nutzerfreundlichkeit zu beeinträchtigen. Wenn android:gwpAsanMode nicht
in einer AndroidManifest.xml angegeben ist, verwendet die App wiederherstellbares
GWP-ASan.
Wiederherstellbares GWP-ASan unterscheidet sich in folgenden Punkten vom Basis-GWP-ASan:
- Wiederherstellbares GWP-ASan wird nur bei etwa 1% der App-Starts aktiviert, nicht bei jedem Start.
- Wenn ein Heap-Use-After-Free- oder Heap-Buffer-Overflow-Fehler erkannt wird, wird er
im Absturzbericht (Tombstone) angezeigt. Dieser Absturzbericht ist verfügbar
über die
ActivityManager#getHistoricalProcessExitReasonsAPI, genau wie beim ursprünglichen GWP-ASan. - Anstatt nach dem Erstellen des Absturzberichts zu beenden, lässt wiederherstellbares 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 Speicherschäden 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 Bericht von wiederherstellbarem GWP-ASan erhalten.
- Wenn in der App ein benutzerdefinierter Signalhandler installiert ist, wird er nie für ein SIGSEGV-Signal aufgerufen, das auf einen Fehler von wiederherstellbarem GWP-ASan hinweist.
Da Abstürze von wiederherstellbarem GWP-ASan auf tatsächliche Fälle von Speicher schäden auf Geräten von Endnutzern hinweisen, empfehlen wir dringend, von wiederherstellbarem GWP-ASan erkannte 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 wie Sie sie beheben können.
Traces für Zuweisung/Freigabe fehlen
Wenn Sie einen nativen Absturz diagnostizieren, bei dem anscheinend Frames für die Zuweisung/Freigabe fehlen, fehlen in Ihrer Anwendung wahrscheinlich Frame-Pointer. GWP-ASan verwendet Frame-Pointer, um aus Leistungsgründen Traces für die Zuweisung und Freigabe aufzuzeichnen. Wenn sie nicht vorhanden sind, kann der Stacktrace nicht aufgelöst 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 für GWP-ASan im Allgemeinen
nicht möglich, Traces für die Zuweisung/Freigabe für 32-Bit
Executables oder ‑Apps zu erfassen. Bei 64-Bit-Anwendungen muss darauf geachtet werden, dass sie 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 Heap-Speichersicherheit auf Nutzergeräten zu erkennen. GWP-ASan liefert so viele Informationen wie möglich zum Absturz (Zugriffstrace des Verstoßes, Ursachenstring und Traces für die Zuweisung/Freigabe). Es kann jedoch schwierig sein, daraus abzuleiten, wie der Verstoß aufgetreten ist. Da die Fehler erkennung probabilistisch ist, sind GWP-ASan-Berichte auf einem lokalen Gerät oft schwer zu reproduzieren.
Wenn der Fehler 64-Bit-Geräte betrifft, sollten Sie in diesen Fällen HWAddressSanitizer (HWASan) verwenden. HWASan erkennt Verstöße gegen die Speichersicherheit zuverlässig im Stack, Heap und in globalen Variablen. Wenn Sie Ihre Anwendung mit HWASan ausführen, kann das gleiche Ergebnis, das von GWP-ASan gemeldet wird, zuverlässig reproduziert werden.
Wenn die Ausführung Ihrer Anwendung mit 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 ausrichten, mit dem zugrunde liegende Probleme mit der Codequalität zuverlässig erkannt und aufgedeckt werden können.
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 illegale Verwendung erfolgreich erkannt und den folgenden Absturzbericht ausgelöst. GWP-ASan hat den Bericht automatisch um Informationen zum Typ des Absturzes, zu den Zuweisungsmetadaten und zu den zugehörigen Stacktraces für die Zuweisung 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 Android-Absturzberichten finden Sie unter Native Abstürze diagnostizieren.