GWP-ASan è una funzionalità di allocazione della memoria nativa che aiuta a 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 del debug malloc, GWP-ASan non richiede il codice sorgente o la ricompilazione (ovvero funziona con i predefiniti) e funziona sia su processi a 32 bit 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 per le app che hanno come target Android 11 (livello API 30) o versioni successive.
Panoramica
GWP-ASan viene attivato su alcune applicazioni di sistema ed eseguibili della piattaforma selezionati in modo casuale all'avvio del processo (o quando lo zigote si biforca). Attiva GWP-ASan nella tua app per trovare 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 of death.
Una volta attivato, GWP-ASan intercetta un sottoinsieme di allocazioni heap scelto in modo casuale e le inserisce in una regione speciale che rileva i bug di danneggiamento della memoria heap difficili da rilevare. Con un numero sufficiente di utenti, anche questa bassa frequenza 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 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ò essere di grande aiuto nel debug (vedi Esempio).
GWP-ASan è progettato per non comportare un overhead significativo della CPU. GWP-ASan introduce un overhead della RAM ridotto e fisso quando è attivato. Questo overhead è deciso dal sistema Android e attualmente è di circa 70 kibibyte (KiB) per ogni processo interessato.
Attiva 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 disattivato (
android:gwpAsanMode="never"): questa impostazione disattiva completamente GWP-ASan nell'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: GWP-ASan recuperabile è abilitato.Sempre attivo (
android:gwpAsanMode="always"): questa impostazione attiva GWP-ASan nella tua app, che include quanto segue: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 della memoria utilizzata.)
GWP-ASan intercetta un sottoinsieme di allocazioni heap scelto in modo casuale e lo inserisce in una regione speciale che rileva in modo affidabile le violazioni della sicurezza della memoria.
Quando si verifica una violazione della sicurezza della memoria nella regione speciale, GWP-ASan termina il processo.
GWP-ASan fornisce informazioni aggiuntive sull'errore nel report sugli arresti anomali.
Per attivare 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 attivato o disattivato esplicitamente per sottoprocessi specifici della tua app. Puoi scegliere come target attività e servizi utilizzando processi che vengono attivati o disattivati esplicitamente per 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 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 da GWP-ASan di base per i seguenti aspetti:
- GWP-ASan recuperabile viene attivato solo in circa l'1% dei lanci dell'app, anziché a ogni lancio.
- 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, come il GWP-ASan originale. - Anziché uscire dopo aver eseguito il dump del report sugli arresti anomali, GWP-ASan recuperabile consente il danneggiamento della memoria e l'app continua a funzionare. Anche se il processo 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 o potrebbe continuare senza alcun impatto visibile per l'utente.
- 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 ogni avvio.
- Se nell'app è installato un gestore di indicatori personalizzato, non viene mai chiamato per un indicatore 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 eseguire il triage e correggere i bug identificati da GWP-ASan recuperabile con una priorità elevata.
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 nell'applicazione manchino i puntatori ai frame. GWP-ASan utilizza i puntatori ai frame per registrare le tracce di allocazione e deallocazione per motivi di prestazioni e non è in grado di eseguire l'unwinding dell'analisi dello stack se non sono presenti.
I puntatori ai 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 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 analisi 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.
Se l'esecuzione dell'applicazione con HWASan non è sufficiente per individuare la causa principale di un bug, prova a eseguire il fuzzing del codice in questione. Puoi scegliere come target le tue attività 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 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'esecuzione di test che utilizza il codice di esempio riportato sopra, GWP-ASan ha rilevato correttamente l'utilizzo non consentito 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 analisi 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 scoprire di più sui dettagli di implementazione di GWP-ASan, consulta la documentazione di LLVM. Per scoprire di più sui report sugli arresti anomali nativi di Android, consulta Diagnosi degli arresti anomali nativi.