GWP - San

GWP-ASan è una funzionalità nativa per l'allocazione della memoria che aiuta a trovare bug use-after-free e heap-buffer-overflow. Il suo nome informale è un acronimo ricorsivo,"GWP-ASan Will Provide Allocation SANity". A differenza di HWASan o di Malloc Debug, GWP-ASan non richiede origine o ricompilazione (ovvero funziona con processi predefiniti) e funziona sia su processi a 32 che a 64 bit (sebbene gli arresti anomali a 32 bit abbiano meno informazioni di debug). Questo argomento descrive le azioni che devi intraprendere per attivare questa funzionalità nella tua app. GWP-ASan è disponibile nelle app destinate ad Android 11 (livello API 30) o versioni successive.

Panoramica

GWP-ASan è abilitato per alcune applicazioni di sistema selezionate in modo casuale ed eseguibili della piattaforma all'avvio del processo (o quando i fork zigote). Attiva GWP-ASan nella tua app per aiutarti a trovare bug relativi alla memoria e per preparare l'app per il supporto delle estensioni MTE (Memory Tagging Extension) ARM. I meccanismi di campionamento dell'allocazione forniscono inoltre affidabilità contro le query relative ai decesso.

Una volta abilitato, GWP-ASan intercetta un sottoinsieme scelto in modo casuale di allocazioni heap e le inserisce in un'area geografica speciale che individua bug difficili da rilevare relativi al danneggiamento della memoria heap. Se hai un numero sufficiente di utenti, anche questa bassa frequenza di campionamento individuerà bug di sicurezza della memoria heap non rilevati tramite test regolari. Ad esempio, GWP-ASan ha rilevato un numero significativo di bug nel browser Chrome (molti dei quali sono ancora soggetti a visualizzazione limitata).

GWP-ASan raccoglie informazioni aggiuntive su tutte le allocazioni che intercetta. Queste informazioni sono disponibili quando GWP-ASan rileva una violazione di sicurezza della memoria e vengono inserite automaticamente nel report sugli arresti anomali nativo, il che può essere notevolmente utile nel debug (vedi Esempio).

GWP-ASan è progettato per non comportare un overhead significativo della CPU. GWP-ASan introduce un piccolo overhead fisso della RAM quando è abilitato. L'overhead viene stabilito dal sistema Android e attualmente è pari a circa 70 kibibyte (KiB) per ogni processo interessato.

Attiva l'app

GWP-ASan può essere attivato dalle app a livello di singolo processo utilizzando il tag android:gwpAsanMode nel file manifest dell'app. Sono supportate le seguenti opzioni:

  • Sempre disattivata (android:gwpAsanMode="never"): questa impostazione disattiva completamente GWP-ASan nella tua app ed è l'impostazione predefinita per le app non di sistema.

  • Valore predefinito (android:gwpAsanMode="default" o non specificato): Android 13 (livello API 33) e versioni precedenti. GWP-ASan è disattivato. Android 14 (livello API 34) e versioni successive. È abilitato il GWP-ASan recuperabile.

  • Sempre abilitata (android:gwpAsanMode="always"): questa impostazione abilita GWP-ASan nella tua app, che include quanto segue:

    1. Il sistema operativo riserva una quantità fissa di RAM per le operazioni GWP-ASan, circa 70 KiB per ogni processo interessato. (Abilita GWP-ASan se la tua app non è eccessivamente sensibile agli aumenti dell'utilizzo della memoria.)

    2. GWP-ASan intercetta un sottoinsieme scelto in modo casuale di allocazioni heap e li inserisce in una regione speciale che rileva in modo affidabile le violazioni di sicurezza della memoria.

    3. Quando si verifica una violazione di sicurezza della memoria nella regione speciale, GWP-ASan termina il processo.

    4. GWP-ASan fornisce informazioni aggiuntive sull'errore nel report sugli arresti anomali.

Per abilitare GWP-ASan a livello globale per la tua app, aggiungi quanto segue al tuo file AndroidManifest.xml:

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

Inoltre, GWP-ASan può essere abilitato o disabilitato esplicitamente per processi secondari specifici dell'app. Puoi scegliere come target attività e servizi utilizzando processi che sono attivati o disattivati esplicitamente in GWP-ASan. Vedi un esempio di seguito:

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

GWP-ASan recuperabile

Android 14 (livello API 34) e versioni successive supportano GWP-ASan recuperabile, che aiuta gli sviluppatori a trovare bug heap-buffer-overflow e heap-use-after-free in produzione senza compromettere l'esperienza utente. Quando android:gwpAsanMode non è specificato in un AndroidManifest.xml, l'app utilizza GWP-ASan recuperabile.

GWP-ASan recuperabile differisce dalla base GWP-ASan per i seguenti modi:

  1. GWP-ASan recuperabile è abilitato solo in circa l'1% dei lanci dell'app, anziché a ogni lancio.
  2. Quando viene rilevato un bug heap-use-after-free o heap-buffer-overflow, questo bug viene visualizzato nel report sugli arresti anomali (tombstone). Questo report sugli arresti anomali è disponibile tramite l'API ActivityManager#getHistoricalProcessExitReasons, la stessa del GWP-ASan originale.
  3. Invece di uscire dopo aver eseguito il dump del report sull'arresto anomalo, Recuperaable GWP-ASan consente il danneggiamento della memoria e l'app continua a essere eseguita. Anche se la procedura potrebbe continuare come di consueto, il comportamento dell'app non è più specificato. A causa del danneggiamento della memoria, in futuro l'app potrebbe arrestarsi in modo anomalo in qualsiasi momento arbitrario oppure continuare senza alcun impatto visibile all'utente.
  4. GWP-ASan recuperabile viene disabilitato dopo l'esecuzione del dump del report sull'arresto anomalo. Pertanto, un'app può ottenere un solo report GWP-ASan recuperabile per ogni lancio dell'app.
  5. Se nell'app è installato un gestore di indicatori personalizzato, non viene mai chiamato un indicatore SIGSEGV indicativo di un errore GWP-ASan recuperabile.

Poiché gli arresti anomali di GWP-ASan recuperabile indicano istanze reali di danni della memoria sui dispositivi degli utenti finali, ti consigliamo vivamente di classificare e correggere i bug identificati da GWP-ASan Recuperabile con priorità elevata.

Assistenza per gli sviluppatori

Queste sezioni descrivono i problemi che potrebbero verificarsi quando si utilizza GWP-ASan e le modalità di risoluzione.

Tracce di allocazione/deallocation mancanti

Se stai diagnosticando un arresto anomalo nativo che sembra privo di frame di allocazione/deallocation, è probabile che nella tua applicazione manchino puntatori frame. GWP-ASan utilizza i puntatori a frame per registrare le analisi di allocazione e deallocation per motivi di prestazioni e non è in grado di annullare l'analisi dello stack se non sono presenti.

I puntatori a frame sono attivi per impostazione predefinita per i dispositivi ARM64 e disattivati per impostazione predefinita per i dispositivi ARM32. Poiché le applicazioni non hanno il controllo su libc, in generale GWP-ASan non può raccogliere tracce di allocazione/deallocation per app o eseguibili a 32 bit. Le applicazioni a 64 bit devono garantire che non siano create con -fomit-frame-pointer, in modo che GWP-ASan possa raccogliere analisi dello stack di allocazione e deal.

Riproduzione di violazioni della sicurezza

GWP-ASan è progettato per rilevare le violazioni di sicurezza della memoria heap sui dispositivi degli utenti. GWP-ASan fornisce quanto più contesto possibile sull'arresto anomalo (traccia di accesso della violazione, stringa di causa e tracce di allocazione/deallocation), ma potrebbe comunque essere difficile dedurre come si è verificata la violazione. Sfortunatamente, poiché il rilevamento dei bug è probabilistici, le segnalazioni GWP-ASan sono spesso difficili da riprodurre su un dispositivo locale.

In questi casi, se il bug interessa i dispositivi a 64 bit, devi utilizzare HWAddressSanitizer (HWASan). HWASan rileva le violazioni di sicurezza della memoria in modo affidabile su stack, heap e globali. L'esecuzione dell'applicazione con HWASan potrebbe riprodurre in modo affidabile lo stesso risultato segnalato da GWP-ASan.

Nei casi in cui l'esecuzione dell'applicazione in HWASan non sia sufficiente per causare un bug root, dovresti provare a fuzz il codice in questione. Puoi scegliere come target le tue iniziative di fuzzing in base alle informazioni contenute nel report GWP-ASan, che può rilevare e individuare in modo affidabile i problemi di integrità del codice sottostanti.

Esempio

Questo codice nativo di esempio presenta un bug heap use-after-free:

#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));
}

Per un'esecuzione di test utilizzando il codice di esempio riportato sopra, GWP-ASan ha rilevato correttamente l'utilizzo illegale e ha attivato il report sugli arresti anomali di seguito. GWP-ASan ha migliorato automaticamente il report fornendo informazioni sul tipo di arresto anomalo, sui metadati di allocazione e sulle tracce associate dello stack di allocazione e distribuzione.

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

Altre informazioni

Per saperne di più sui dettagli di implementazione di GWP-ASan, consulta la documentazione LLM. Per scoprire di più sui report sugli arresti anomali nativi di Android, consulta Diagnosi degli arresti anomali nativi.