GWP-ASan

GWP-ASan ist eine native Arbeitsspeicherzuweisungsfunktion, die dabei hilft, Programmfehler von use-after-free und heap-buffer-overflow zu finden. 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 und keine Neukompilierung (d. h., er funktioniert mit vordefinierten Modellen) und funktioniert sowohl mit 32- als auch mit 64-Bit-Prozessen (obwohl 32-Bit-Abstürze weniger Informationen zur Fehlerbehebung 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 in einigen zufällig ausgewählten Systemanwendungen und ausführbaren Plattformen beim Starten des Prozesses (oder beim Verzweigen der Zygote) aktiviert. Aktivieren Sie GWP-ASan in Ihrer eigenen Anwendung, um speicherbezogene Fehler zu finden und Ihre Anwendung auf die Unterstützung der ARM-Arbeitsspeicher-Tagging-Erweiterung (MTE) vorzubereiten. Die Mechanismen der Stichprobenerhebung sind auch bei Todesfällen zuverlässig.

Nach der Aktivierung fängt GWP-ASan eine zufällig ausgewählte Teilmenge von Heap-Zuweisungen ab und platziert sie in einer speziellen Region, in der schwer zu erkennende Fehler aufgrund von Heap-Speicherschäden erkannt werden. Wenn genügend Nutzer vorhanden sind, werden selbst bei dieser niedrigen Abtastrate sicherheitsrelevante Heap-Speicherfehler gefunden, die durch regelmäßige Tests nicht gefunden werden. So hat GWP-ASan beispielsweise eine große Anzahl von Fehlern im Chrome-Browser gefunden (viele davon befinden sich immer noch in der eingeschränkten Ansicht).

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

GWP-ASan ist so konzipiert, dass es keinen nennenswerten CPU-Aufwand verursacht. GWP-ASan führt bei Aktivierung zu einem kleinen, festen RAM-Aufwand. Dieser Aufwand wird vom Android-System bestimmt und beträgt derzeit etwa 70 Kibibyte (KiB) für jeden betroffenen Prozess.

App aktivieren

GWP-ASan kann von Anwendungen für einzelne Prozesse mithilfe des Tags android:gwpAsanMode im App-Manifest aktiviert werden. Folgende Optionen werden unterstützt:

  • Immer deaktiviert (android:gwpAsanMode="never"): Mit dieser Einstellung wird GWP-ASan in Ihrer Anwendung vollständig deaktiviert. Dies ist die Standardeinstellung für systemfremde 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 Anwendung aktiviert. Dies umfasst Folgendes:

    1. Das Betriebssystem reserviert eine feste Menge an RAM für GWP-ASan-Vorgänge, etwa ca. 70 KiB für jeden betroffenen Prozess. (Aktivieren Sie GWP-ASan, wenn Ihre Anwendung nicht kritisch empfindlich auf erhöhte Arbeitsspeichernutzung reagiert.)

    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 Sicherheitsverstöße des Arbeitsspeichers erkennt.

    3. Wenn ein Speichersicherheitsverstoß in der speziellen Region auftritt, beendet GWP-ASan den Prozess.

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

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

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

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

Wiederherstellbares GWP-ASan

Android 14 (API-Level 34) und höher unterstützen den Wiederherstellen von 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 Anwendung das wiederherstellbare GWP-ASan.

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

  1. Das wiederherstellbare GWP-ASan ist nur bei etwa 1% der Anwendungsstarts aktiviert, und nicht bei jedem Start einer Anwendung.
  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, genau wie das ursprüngliche GWP-ASan.
  3. Der wiederherstellbare GWP-ASan muss nicht nach dem Erstellen des Absturzes beendet werden. Stattdessen wird der Arbeitsspeicher beschädigt, sodass die Anwendung weiterhin ausgeführt wird. Der Prozess kann zwar wie gewohnt fortgesetzt werden, das Verhalten der Anwendung wird jedoch nicht mehr angegeben. Aufgrund der Speicherbeschädigung kann die App an einem beliebigen Punkt in der Zukunft abstürzen oder ohne sichtbare Auswirkungen für den Nutzer fortgesetzt werden.
  4. Das wiederherstellbare GWP-ASan wird deaktiviert, nachdem der Absturzbericht erstellt wurde. Daher kann pro App-Start nur ein einziger GWP-ASan-Bericht abgerufen werden.
  5. Wenn in der Anwendung ein benutzerdefinierter Signal-Handler installiert ist, wird er nie für ein SIGSEGV-Signal aufgerufen, das auf einen wiederherstellbaren GWP-ASan-Fehler hinweist.

Da wiederherstellbare GWP-ASan-Abstürze auf reale Instanzen von Speicherschäden auf Endnutzergeräten hinweisen, empfehlen wir dringend, Fehler zu diagnostizieren und zu beheben, die vom 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 sie beheben können.

Zuweisungs-/Zuweisungs-Traces fehlen

Wenn Sie einen nativen Absturz diagnostizieren, bei dem Zuordnungs-/Entzugsframes zu fehlen scheinen, fehlen in Ihrer Anwendung wahrscheinlich Framezeiger. GWP-ASan verwendet Framezeiger zur Aufzeichnung von Zuweisungs- und Freigabe-Traces aus Leistungsgründen und kann den Stacktrace nicht lösen, wenn sie nicht vorhanden sind.

Framezeiger 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, Zuordnungs-/Freigabe-Traces für ausführbare 32-Bit-Dateien oder -Anwendungen zu erfassen. 64-Bit-Anwendungen sollten nicht mit -fomit-frame-pointer erstellt werden, damit GWP-ASan Stacktraces für die Zuweisung und Zuweisung erfassen kann.

Reproduzieren von Sicherheitsverstößen

GWP-ASan wurde entwickelt, um Sicherheitsverstöße von Heap-Speicher auf Nutzergeräten zu erkennen. GWP-ASan stellt so viel Kontext wie möglich zum Absturz bereit (Zugriffs-Trace des Verstoßes, Ursache des Strings und Zuweisungs-/Zuweisungs-Traces), aber es ist möglicherweise immer noch schwer nachzuvollziehen, wie der Verstoß aufgetreten ist. Da die Fehlererkennung probabilistisch ist, ist es oft schwierig, GWP-ASan-Berichte 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 Sicherheitsverstöße des Arbeitsspeichers zuverlässig auf Stack-, Heap- und globalen Instanzen. Wenn Sie Ihre Anwendung mit HWASan ausführen, reproduzieren Sie möglicherweise zuverlässig das Ergebnis, das von GWP-ASan gemeldet wird.

In Fällen, in denen das Ausführen Ihrer Anwendung unter HWASan nicht ausreicht, um einen Fehler zu verursachen, sollten Sie versuchen, den betreffenden Code mit Fazzing zu versehen. Sie können Ihre Fuzzing-Maßnahmen anhand der Informationen im GWP-ASan-Bericht ausrichten, der zugrunde liegende Probleme mit dem Codezustand zuverlässig erkennen und aufdecken kann.

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 obigen Beispielcode hat GWP-ASan erfolgreich die illegale Nutzung erkannt und den folgenden Absturzbericht ausgelöst. GWP-ASan hat den Bericht automatisch verbessert, indem er Informationen über die Art des Absturzes, die Zuweisungsmetadaten und die zugehörigen Stacktraces für die Zuordnung und die Freigabe bereitgestellt hat.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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 zur Implementierung von GWP-ASan finden Sie in der LLVM-Dokumentation. Weitere Informationen zu nativen Android-Absturzberichten finden Sie unter Native Abstürze diagnostizieren.