GWP-ASan

GWP-ASan ist eine native Funktion zur Arbeitsspeicherzuweisung, mit der Fehler im Zusammenhang mit Use-After-Free und Heap-Buffer-Overflow gefunden werden können. Der informelle Name ist ein rekursives Akronym: GWP-ASan Will Provide Allocation SANity". Im Gegensatz zu HWASan oder Malloc Debug ist für GWP-ASan weder eine Quelle noch eine Neukompilierung erforderlich (d. h., es funktioniert mit vordefinierten) und funktioniert sowohl mit 32- als auch 64-Bit-Prozessen (auch wenn 32-Bit-Abstürze weniger Debugging-Informationen enthalten). In diesem Artikel werden die Aktionen 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 bei einigen zufällig ausgewählten Systemanwendungen und Plattformen, die ausführbar sind, beim Start des Prozesses (oder beim Verzweigen der Zygote) 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 der Stichprobenerhebung für die Zuordnung bieten auch Zuverlässigkeit bei Todesabfragen.

Nach der Aktivierung fängt GWP-ASan eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einer speziellen Region, die schwer zu erkennende Fehler aufgrund von Heap-Speicherbeschädigungen erkennt. Wenn genügend Nutzer vorhanden sind, werden selbst bei dieser niedrigen Sampling-Rate Sicherheitslücken im Heap-Speicher 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 eingeschränkt sind.

GWP-ASan erfasst zusätzliche Informationen zu allen Zuweisungen, die es abfängt. Diese Informationen sind verfügbar, wenn GWP-ASan einen Sicherheitsverstoß erkennt, und werden automatisch in den nativen Absturzbericht aufgenommen, der bei der Fehlerbehebung erheblich helfen kann (siehe Beispiel).

GWP-ASan ist darauf ausgelegt, keinen nennenswerten CPU-Aufwand zu verursachen. Wenn GWP-ASan aktiviert ist, führt dies zu einem geringen, festen RAM-Overhead. Dieser Mehraufwand wird vom Android-System festgelegt und beträgt derzeit etwa 70 Kibibyte (KiB) für jeden betroffenen Prozess.

App aktivieren

GWP-ASan kann von Apps pro Prozessebene mithilfe des android:gwpAsanMode-Tags 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 systemfremde Apps.

  • Standardeinstellung (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: Wiederherstellbarer GWP-ASan ist aktiviert.

  • Immer aktiviert (android:gwpAsanMode="always"): Mit dieser Einstellung wird GWP-ASan in Ihrer Anwendung aktiviert. Dies umfasst Folgendes:

    1. Das Betriebssystem reserviert eine feste Menge an RAM für GWP-ASan-Vorgänge, ca. 70 KiB für jeden betroffenen Prozess. Aktivieren Sie GWP-ASan, wenn Ihre Anwendung hinsichtlich einer erhöhten Arbeitsspeichernutzung nicht kritisch empfindlich ist.

    2. GWP-ASan fängt eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einer speziellen Region, die zuverlässig Verstöße gegen die Speichersicherheit erkennt.

    3. Wenn in der speziellen Region ein Verstoß gegen die Speichersicherheit auftritt, beendet GWP-ASan den Prozess.

    4. GWP-ASan stellt zusätzliche Informationen zum Fehler im Absturzbericht bereit.

Fügen Sie der Datei AndroidManifest.xml Folgendes hinzu, um GWP-ASan global für Ihre Anwendung zu aktivieren:

<application android:gwpAsanMode="always">
  ...
</application>

Darüber hinaus kann GWP-ASan für bestimmte Unterprozesse Ihrer Anwendung explizit aktiviert oder deaktiviert werden. Sie können ein Targeting auf Aktivitäten und Dienste mithilfe von Prozessen vornehmen, bei denen GWP-ASan explizit aktiviert oder deaktiviert ist. 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

Android 14 (API-Level 34) und höher unterstützen wiederherstellbaren GWP-ASan, der Entwicklern hilft, Heap-Pufferüberlauf und Heap-Use-After-Free-Fehler in der Produktion zu finden, ohne die Nutzererfahrung zu beeinträchtigen. Wenn android:gwpAsanMode in einer AndroidManifest.xml nicht angegeben ist, verwendet die Anwendung wiederherstellbare GWP-ASan.

Der wiederherstellbare GWP-ASan unterscheidet sich in folgenden Punkten vom Basis-GWP-ASan:

  1. Wiederherstellbarer GWP-ASan wird nur bei etwa 1% der Anwendungsstarts und nicht bei jedem Anwendungsstart aktiviert.
  2. Wenn ein Heap-Use-After-Free- oder Heap-Buffer-Overflow-Fehler erkannt wird, wird dieser Fehler im Absturzbericht (Tombstone) angezeigt. Dieser Absturzbericht ist über die ActivityManager#getHistoricalProcessExitReasons API verfügbar, die auch dem ursprünglichen GWP-ASan entspricht.
  3. Der Recoveryable GWP-ASan wird nach dem Speichern des Absturzberichts nicht beendet, sodass der Speicher beschädigt werden kann. Die Anwendung wird dann weiter ausgeführt. Der Prozess kann wie gewohnt fortgesetzt werden, das Verhalten der Anwendung ist jedoch nicht mehr angegeben. Aufgrund der Speicherbeschädigung kann die App zu einem beliebigen Zeitpunkt in Zukunft abstürzen oder ohne sichtbare Auswirkungen für den Nutzer fortgesetzt werden.
  4. Der wiederherstellbare GWP-ASan wird nach dem Erstellen des Absturzberichts deaktiviert. Daher kann eine Anwendung pro Anwendungsstart nur einen einzigen wiederherstellbaren GWP-ASan-Bericht abrufen.
  5. Wenn ein benutzerdefinierter Signal-Handler in der App installiert ist, wird er nie für ein SIGSEGV-Signal aufgerufen, das auf einen Restoreable GWP-ASan-Fehler hindeutet.

Da wiederherstellbare GWP-ASan-Abstürze auf echte Instanzen von Speicherbeschädigungen auf Endnutzergeräten hinweisen, empfehlen wir dringend, Fehler zu untersuchen und zu beheben, die von wiederherstellbaren GWP-ASan mit hoher Priorität identifiziert wurden.

Support für Entwickler

In diesen Abschnitten werden Probleme beschrieben, die bei der Verwendung von GWP-ASan auftreten können, und wie Sie diese beheben.

Zuordnungs-/Weiterleitungs-Traces fehlen

Wenn Sie einen nativen Absturz diagnostizieren, bei dem offenbar Frames für die Zuweisung/Umverteilung fehlen, fehlen in Ihrer Anwendung wahrscheinlich Frame-Pointer. GWP-ASan verwendet Frame-Points, um Traces für die Zuweisung und Freigabe der Standortermittlung aus Leistungsgründen aufzuzeichnen. Wenn sie nicht vorhanden sind, kann der Stacktrace nicht aufgelöst werden.

Framepointer 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 von 32-Bit-Dateien oder -Anwendungen zu erfassen. Bei 64-Bit-Anwendungen muss darauf geachtet werden, dass sie nicht mit -fomit-frame-pointer erstellt werden, damit GWP-ASan Zuordnungs- und Stacktraces erfassen kann.

Sicherheitsverstöße reproduzieren

GWP-ASan wurde entwickelt, um Verstöße gegen die Heap-Speicher-Sicherheit auf Nutzergeräten zu erkennen. GWP-ASan stellt so viel Kontext wie möglich zum Absturz bereit (Zugriffs-Trace des Verstoßes, Ursache-String und Traces für Zuordnung/Weitergabe), aber es kann dennoch schwierig sein, die Ursache des Verstoßes zu ermitteln. Da die Fehlererkennung probabilistisch ist, sind GWP-ASan-Berichte häufig schwierig auf einem lokalen Gerät zu reproduzieren.

Wenn der Fehler in diesen Fällen 64-Bit-Geräte betrifft, sollten Sie HWAddressSanitizer (HWASan) verwenden. HWASan erkennt zuverlässig Sicherheitsverstöße des Arbeitsspeichers auf Stack, Heap und globalen Daten. Bei der Ausführung Ihrer Anwendung mit HWASan kann zuverlässig das Ergebnis reproduziert werden, das von GWP-ASan gemeldet wird.

Wenn die Ausführung Ihrer Anwendung unter HWASan nicht ausreicht, um einen Fehler zu verursachen, sollten Sie versuchen, den betreffenden Code zu fuzzieren. Sie können Ihre Fuzzing-Aktivitäten anhand der Informationen aus dem GWP-ASan-Bericht ausrichten, mit dem zugrunde liegende Codeprobleme zuverlässig erkannt und aufgedeckt werden.

Beispiel

Dieser native Beispielcode hat 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 Test mit dem Beispielcode oben konnte GWP-ASan die illegale Nutzung erfolgreich abfangen und den unten aufgeführten Absturzbericht ausgelöst. GWP-ASan hat den Bericht automatisch optimiert, indem Informationen zum Absturztyp, zu den Zuordnungsmetadaten und den zugehörigen Zuordnungs- und Freigabe-Stacktraces bereitgestellt 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 Android-Absturzberichten finden Sie unter Native Abstürze diagnostizieren.