GWP-ASan è una funzionalità di allocazione della memoria nativa che consente di trovare bug di uso dopo svuotamento e di overflow del buffer dell'heap. Il nome informale è un acronimo ricorsivo,"GWP-ASan Will Provide Allocation SANity". A differenza di HWASan o Malloc Debug, GWP-ASan non richiede il codice sorgente o la ricompilarzione (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 illustra le azioni da 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 alcune applicazioni di sistema e file eseguibili della piattaforma selezionati in modo casuale all'avvio del processo (o quando si verifica il fork di zygote). Attiva GWP-ASan nella tua app per aiutarti a trovare bug relativi alla memoria e per prepararla al supporto di MTE (Memory Tagging Extension) ARM. I meccanismi di campionamento dell'allocazione offrono inoltre affidabilità contro le query di morte.
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 bug di corruzione della memoria dell'heap difficili da rilevare. Con un numero sufficiente di utenti, anche questa bassa frequenza di campionamento consente di trovare 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 di sicurezza della memoria e vengono inserite automaticamente nel report sugli arresti anomali nativi, il che può contribuire notevolmente al debug (vedi Esempio).
GWP-ASan è progettato per non comportare un overhead della CPU significativo. GWP-ASan introduce un piccolo overhead di RAM fisso se è attivato. Questo overhead è deciso dal sistema Android e attualmente è pari a circa 70 kibibyte (KiB) per ogni processo interessato.
Attivare l'app
GWP-ASan può essere attivato dalle app a livello di processo utilizzando il
android:gwpAsanMode
tag nel file manifest dell'app. Sono supportate le seguenti opzioni:
Sempre disabilitato (
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 è disabilitato. Android 14 (livello API 34) e versioni superiori: GWP-ASan recuperabile è abilitato.Sempre abilitato (
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 KB per ogni processo interessato. Attiva GWP-ASan se la tua app non è estremamente sensibile agli aumenti dell'utilizzo della memoria.
GWP-ASan intercetta un sottoinsieme scelto in modo casuale di allocazioni dell'heap 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
AndroidManifest.xml
file:
<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 per i quali è stata attivata o disattivata esplicitamente la funzionalità GWP-ASan. Ecco 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 peggiorare l'esperienza utente. Quando android:gwpAsanMode
non è specificato in un AndroidManifest.xml
, l'app utilizza GWP-ASan recuperabile.
GWP-ASan recuperabile è diverso da GWP-ASan di base nei seguenti modi:
- GWP-ASan recuperabile è abilitato solo in circa l'1% dei lanci di app, non in ogni lancio dell'applicazione.
- 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 per GWP-ASan originale. - Invece di uscire dopo aver dumpato il report sugli arresti anomali, GWP-ASan recuperabile consente il danneggiamento della memoria e l'app continua a funzionare. Sebbene la procedura possa continuare come di consueto, il comportamento dell'app non è più specificato. A causa della corruzione della memoria, l'app potrebbe arrestarsi in modo anomalo in un punto 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 dell'app.
- 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 GWP-ASan recuperabili indicano istanze reali di danneggiamento della memoria sui dispositivi degli utenti finali, ti consigliamo vivamente di eseguire la triage e correggere i bug identificati da GWP-ASan recuperabile con una priorità elevata.
Assistenza per gli sviluppatori
Queste sezioni descrivono i problemi che potrebbero verificarsi durante l'utilizzo di GWP-ASan e come risolverli.
Mancano le tracce di allocazione/deallocazione
Se stai diagnosticando un arresto anomalo nativo per il quale sembrano mancare frame di allocazione/deallocazione, è probabile che nella tua applicazione manchino puntatori frame. GWP-ASan utilizza gli indicatori frame per registrare le tracce di allocazione e deallocazione per motivi di prestazioni e non è in grado di annullare la traccia dello stack se non sono presenti.
I puntatori 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 GWP-ASan non può raccogliere tracce di allocazione/deallocazione per app o eseguibili a 32 bit. Le applicazioni a 64 bit devono essere non
costruite con -fomit-frame-pointer
in modo che GWP-ASan possa raccogliere le tracce dello stack di allocazione e deallocazione.
Riprodurre violazioni di 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 di causa e tracce di allocazione/deallocazione), ma potrebbe essere comunque difficile dedurre in che modo 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 in HWASan non è sufficiente per identificare la causa principale di un bug, devi provare a fuzzare il codice in questione. Puoi scegliere come target le attività di fuzzing in base alle informazioni nel report GWP-ASan, che può rilevare e rivelare in modo affidabile i problemi di salute del codice di base.
Esempio
Questo codice nativo di esempio presenta un bug di uso dopo svuotamento 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 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 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 dell'implementazione di GWP-ASan, consulta la documentazione di LLVM. Per saperne di più sui report sugli arresti anomali nativi di Android, consulta Diagnostica degli arresti anomali nativi.