GWP-ASan

GWP-ASan ist eine native Speicherallokationsfunktion, 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 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 Thema wird beschrieben, welche Schritte Sie ausführen müssen, um diese Funktion in Ihrer App zu aktivieren. GWP-ASan ist in 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 Plattformausführbaren beim Prozessstart (oder beim Ausführen der Zygote) aktiviert. Aktivieren Sie GWP-ASan in Ihrer eigenen App, um Speicherfehler zu finden und Ihre App auf die Unterstützung der ARM Memory Tagging Extension (MTE) vorzubereiten. Die Zufallsstichprobenmechanismen bieten auch Schutz vor Abfragen, die zu keinem Ergebnis führen.

Nach der Aktivierung fängt GWP-ASan eine zufällig ausgewählte Teilmenge der Heap-Zuweisungen ab und platziert sie in einem speziellen Bereich, in dem schwer zu erkennende Fehler bei der Beschädigung des Heap-Speichers erfasst werden. Bei ausreichend vielen Nutzern werden mit dieser niedrigen Stichprobenrate sogar Sicherheitslücken im Heap-Speicher gefunden, die bei regelmäßigen Tests nicht entdeckt werden. So hat GWP-ASan beispielsweise eine erhebliche Anzahl von Fehlern im Chrome-Browser gefunden, von denen viele noch nicht öffentlich zugänglich 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 die Fehlerbehebung erheblich erleichtern kann (siehe Beispiel).

GWP-ASan ist darauf ausgelegt, keinen nennenswerten CPU-Aufwand zu verursachen. 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) pro betroffenem Prozess.

App aktivieren

GWP-ASan kann von Apps auf Prozessebene aktiviert werden, indem das Tag android:gwpAsanMode im App-Manifest verwendet wird. 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 systemeigene 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: Wiederherstellbare GWP-ASan ist aktiviert.

  • Immer aktiviert (android:gwpAsanMode="always"): Mit dieser Einstellung wird GWP-ASan in Ihrer App aktiviert. Dazu gehören:

    1. 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 Anwendung hinsichtlich einer erhöhten Arbeitsspeichernutzung nicht kritisch empfindlich ist.

    2. GWP-ASan fängt eine zufällig ausgewählte Teilmenge der Heap-Zuweisungen ab und platziert sie in einer speziellen Region, in der Speichersicherheitsverstöße zuverlässig erkannt werden.

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

    4. 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>

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 GWP-ASan mit Wiederherstellung, 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 in einer AndroidManifest.xml nicht angegeben ist, verwendet die App Recoverable GWP-ASan.

Wiederherstellbare GWP-ASan-Daten unterscheiden sich in folgenden Punkten von GWP-ASan-Basisdaten:

  1. Die Wiederherstellbare GWP-ASan-Funktion ist nur bei etwa 1 % der App-Starts aktiviert, nicht bei jedem.
  2. Wenn ein Heap-Use-After-Free- oder Heap-Buffer-Overflow-Fehler erkannt wird, wird dieser Fehler im Absturzbericht (Tombstone) aufgeführt. Dieser Absturzbericht ist über die ActivityManager#getHistoricalProcessExitReasons API verfügbar, genau wie der ursprüngliche GWP-ASan-Bericht.
  3. Anstatt nach dem Dumpen des Absturzberichts zu beenden, ermöglicht die wiederherstellbare GWP-ASan-Version Speicherbeschädigungen und die App wird weiter ausgeführt. Der Vorgang wird möglicherweise wie gewohnt fortgesetzt, das Verhalten der App ist jedoch nicht mehr festgelegt. Aufgrund der Beschädigung des Arbeitsspeichers kann die App zu einem beliebigen Zeitpunkt in Zukunft abstürzen oder ohne für den Nutzer sichtbare Auswirkungen fortgesetzt werden.
  4. Wiederherstellbare GWP-ASan-Daten werden deaktiviert, nachdem der Absturzbericht gedumpt wurde. Daher kann eine App pro App-Start nur einen einzigen wiederherstellbaren GWP-ASan-Bericht erhalten.
  5. Wenn in der App ein benutzerdefinierter Signalhandler 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 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.

Entwicklersupport

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

Es fehlen Zuweisungs-/Entfernungs-Traces

Wenn Sie einen nativen Absturz diagnostizieren, bei dem Frames für die Zuweisung/Deaktivierung fehlen, fehlen Ihrer Anwendung wahrscheinlich Frame-Pointer. GWP-ASan verwendet Frame-Pointer, um aus Leistungsgründen Zuweisungs- und Deaktivierungsspuren aufzuzeichnen. Wenn diese nicht vorhanden sind, kann der Stack-Trace nicht zurückgerollt 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 die libc haben, ist es für GWP-ASan im Allgemeinen nicht möglich, Zuweisungs-/Entfernungsspuren für 32-Bit-Ausführbare oder ‑Apps zu erfassen. 64-Bit-Anwendungen sollten nicht mit -fomit-frame-pointer erstellt werden, damit GWP-ASan Stack-Traces für die Zuweisung und Deaktivierung erfassen kann.

Sicherheitsverstöße reproduzieren

GWP-ASan wurde entwickelt, um Verstöße gegen die Heap-Speicher-Sicherheit auf Nutzergeräten zu erkennen. GWP-ASan liefert so viele Informationen wie möglich zum Absturz (Zugriffsverlauf des Verstoßes, Ursache und Zuweisungs-/Entfernungsverlauf). Es kann jedoch schwierig sein, daraus abzuleiten, wie der Verstoß aufgetreten ist. Da die Fehlererkennung probabilistisch ist, ist es leider 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 Speichersicherheitsverstöße zuverlässig im Stack, Heap und in globalen Variablen. Wenn Sie Ihre Anwendung mit HWASan ausführen, können Sie das Ergebnis, das von GWP-ASan gemeldet wird, zuverlässig reproduzieren.

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-Bemühungen anhand der Informationen im GWP-ASan-Bericht ausrichten, mit dem sich zugrunde liegende Probleme mit dem Code-Zustand zuverlässig erkennen und aufdecken lassen.

Verwendungsbeispiele

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 Testlauf mit dem Beispielcode oben hat GWP-ASan die illegale Nutzung erkannt und den unten stehenden Absturzbericht ausgelöst. GWP-ASan hat den Bericht automatisch um Informationen zum Absturztyp, zu den Allokationsmetadaten und zu den zugehörigen Allokations- und Dealokations-Stack-Traces ergänzt.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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.