GWP - San

GWP-ASan è una funzionalità di allocazione della memoria nativa che aiuta a trovare bug di 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 precompilati) e funziona sia su processi a 32 che 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 attivare 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 alcuni eseguibili di sistema e della piattaforma selezionati in modo casuale all'avvio del processo (o quando viene creato un fork di zygote). Attiva GWP-ASan nella tua app per trovare bug correlati alla memoria e preparare l'app per il supporto dell'estensione di tagging della memoria (MTE) ARM. I meccanismi di campionamento dell'allocazione forniscono anche affidabilità contro le query of death.

Una volta attivato, GWP-ASan intercetta un sottoinsieme scelto in modo casuale di allocazioni dell'heap 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 questo basso tasso di campionamento troverà bug di sicurezza della memoria heap che non vengono rilevati tramite i test regolari. Ad esempio, GWP-ASan ha rilevato un numero significativo di bug nel browser Chrome (molti dei quali sono ancora in visualizzazione con limitazioni).

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 nativo, il che può essere di grande aiuto per il debug (vedi Esempio).

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

Attivare l'opzione per la tua app

GWP-ASan può essere attivato 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 è disattivato. Android 14 (livello API 34) e versioni successive: è abilitato GWP-ASan recuperabile.

  • 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. (Attiva GWP-ASan se la tua app non è particolarmente sensibile agli aumenti dell'utilizzo della memoria.)

    2. GWP-ASan intercetta un sottoinsieme scelto in modo casuale di allocazioni dell'heap 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 specifici sottoprocessi della tua app. Puoi scegliere come target attività e servizi utilizzando processi che hanno attivato o disattivato esplicitamente GWP-ASan. Vedi di seguito 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 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 si differenzia da GWP-ASan di base per i seguenti aspetti:

  1. GWP-ASan recuperabile è abilitato solo per circa l'1% degli avvii di app, anziché per 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'API ActivityManager#getHistoricalProcessExitReasons, come l'originale GWP-ASan.
  3. Anziché uscire dopo aver scaricato il report sugli arresti anomali, Recoverable 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, l'app potrebbe arrestarsi in modo anomalo in un momento arbitrario in futuro oppure potrebbe continuare senza alcun impatto visibile all'utente.
  4. GWP-ASan recuperabile viene disattivato dopo il dump del report sugli arresti anomali. Pertanto, un'app può ricevere un solo report GWP-ASan recuperabile per 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 recuperabili di GWP-ASan indicano istanze reali di danneggiamento della memoria sui dispositivi degli utenti finali, ti consigliamo vivamente di eseguire la valutazione e la correzione dei bug identificati da GWP-ASan recuperabile con una priorità elevata.

Assistenza per gli sviluppatori

Queste sezioni descrivono i problemi che potrebbero verificarsi quando utilizzi GWP-ASan e come risolverli.

Le tracce di allocazione/deallocazione sono mancanti

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

I puntatori di frame sono attivi per impostazione predefinita per i dispositivi arm64 e disattivi per impostazione predefinita per i dispositivi arm32. Poiché le applicazioni non hanno il controllo su libc, in generale non è possibile per GWP-ASan raccogliere tracce di allocazione/deallocazione per 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 di violazioni della sicurezza

GWP-ASan è progettato per rilevare violazioni della sicurezza della memoria heap sui dispositivi degli utenti. GWP-ASan fornisce il maggior contesto possibile sull'arresto anomalo (traccia di accesso della violazione, stringa della 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 su stack, heap e 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 indirizzare 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 sottostanti.

Esempio

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

#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 test di esecuzione utilizzando il codice di esempio riportato sopra, GWP-ASan ha rilevato correttamente l'utilizzo illegale 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 associato.

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