GWP - San

GWP-ASan è una funzionalità di allocazione della memoria nativa che consente di trovare bug di tipo 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 Malloc Debug, GWP-ASan non richiede l'origine o la ricompilazione (ovvero funziona con le precompilazioni) e funziona sia con i processi a 32 bit sia con quelli a 64 bit (anche se gli arresti anomali a 32 bit hanno meno informazioni di debug). Questo argomento descrive le azioni che devi intraprendere per abilitare questa funzionalità nella tua app. GWP-ASan è disponibile nelle app che hanno come target Android 11 (livello API 30) o versioni successive.

Panoramica

GWP-ASan è abilitato su alcune applicazioni di sistema e file eseguibili della piattaforma selezionati in modo casuale all'avvio del processo (o quando il processo zygote viene suddiviso). Abilita GWP-ASan nella tua app per trovare i bug relativi alla memoria e preparare l'app per il supporto di ARM Memory Tagging Extension (MTE). I meccanismi di campionamento dell'allocazione forniscono anche affidabilità contro le query di terminazione.

Una volta abilitato, GWP-ASan intercetta un sottoinsieme di allocazioni dell'heap scelto in modo casuale e le inserisce in una regione speciale che rileva i bug di danneggiamento della memoria dell'heap difficili da rilevare. Con un numero sufficiente di utenti, anche questa bassa frequenza di campionamento troverà i bug di sicurezza della memoria dell'heap che non vengono rilevati tramite i test regolari. Ad esempio, GWP-ASan ha trovato un numero significativo di bug nel browser Chrome (molti dei quali sono ancora in visualizzazione limitata).

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

GWP-ASan è progettato per non comportare un overhead della CPU significativo. Quando è abilitato, GWP-ASan introduce un overhead della RAM piccolo e fisso. Questo overhead viene deciso dal sistema Android ed è attualmente di circa 70 kibibyte (KiB) per ogni processo interessato.

Attivare la funzionalità per la tua app

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

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

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

  • Sempre abilitato (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 è particolarmente sensibile agli aumenti dell'utilizzo della memoria.)

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

    3. Quando si verifica una violazione della 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 file AndroidManifest.xml:

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

Inoltre, GWP-ASan può essere abilitato o disabilitato in modo esplicito per i sottoprocessi specifici della tua app. Puoi scegliere come target attività e servizi utilizzando processi che sono stati inclusi o esclusi in modo esplicito da GWP-ASan. Di seguito è riportato un esempio:

<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 di tipo heap-buffer-overflow e heap-use-after-free in produzione senza compromettere l'esperienza utente. Quando android:gwpAsanMode non è specificato in un file AndroidManifest.xml, l'app utilizza GWP-ASan recuperabile.

GWP-ASan recuperabile differisce da GWP-ASan di base nei seguenti modi:

  1. GWP-ASan recuperabile è abilitato solo su circa l'1% degli avvii delle app, anziché su ogni avvio dell'applicazione.
  2. Quando viene rilevato un bug di tipo 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' ActivityManager#getHistoricalProcessExitReasons API, proprio come GWP-ASan originale.
  3. Anziché uscire dopo aver scaricato il report sugli arresti anomali, GWP-ASan recuperabile consente il danneggiamento della memoria e l'app continua a essere eseguita. Anche se il processo può continuare normalmente, il comportamento dell'app non è più specificato. A causa del danneggiamento della memoria, l'app potrebbe arrestarsi in modo anomalo in un momento arbitrario in futuro oppure potrebbe continuare senza alcun impatto visibile per l'utente.
  4. GWP-ASan recuperabile viene disabilitato dopo il download del report sugli arresti anomali. Di conseguenza, un'app può ricevere un solo report GWP-ASan recuperabile per ogni avvio dell'app.
  5. Se nell'app è installato un gestore di segnali personalizzato, non viene mai chiamato per un segnale SIGSEGV che indica un errore GWP-ASan recuperabile.

Poiché gli arresti anomali di GWP-ASan recuperabile indicano istanze reali di danneggiamento della memoria sui dispositivi degli utenti finali, ti consigliamo vivamente di assegnare la priorità alla risoluzione dei bug identificati da GWP-ASan recuperabile.

Assistenza per sviluppatori

Queste sezioni descrivono i problemi che potrebbero verificarsi durante l'utilizzo di GWP-ASan e come risolverli.

Tracce di allocazione/deallocazione mancanti

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

I puntatori di 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 genere non è possibile per GWP-ASan raccogliere tracce di allocazione/deallocazione per file eseguibili o app a 32 bit. Le applicazioni a 64 bit devono assicurarsi di non essere create con -fomit-frame-pointer in modo che GWP-ASan possa raccogliere le tracce dello stack di allocazione e deallocazione.

Riproduzione delle violazioni della sicurezza

GWP-ASan è progettato per rilevare le violazioni della sicurezza della memoria dell'heap sui dispositivi degli utenti. GWP-ASan fornisce il maggior contesto possibile sull'arresto anomalo (traccia di accesso della violazione, stringa di causa e tracce di allocazione/deallocazione), ma potrebbe comunque essere difficile dedurre come si è verificata la violazione. Purtroppo, poiché il rilevamento dei bug è probabilistico, i report 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 in modo affidabile le violazioni della sicurezza della memoria nello stack, nell'heap e nelle variabili 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 è sufficiente per individuare la causa principale di un bug, devi provare a eseguire il fuzzing del codice in questione. Puoi scegliere come target i tuoi sforzi di fuzzing in base alle informazioni contenute nel report GWP-ASan, che può rilevare e rivelare in modo affidabile i problemi di integrità del codice sottostante.

Esempio

Questo codice nativo di esempio presenta un bug di tipo 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 illecito e ha attivato il report sugli arresti anomali riportato di seguito. GWP-ASan ha migliorato automaticamente il report fornendo informazioni sul tipo di arresto anomalo, sui metadati di allocazione e sulle tracce dello stack di allocazione e deallocazione associate.

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

Ulteriori informazioni

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