AddressSanitizer (ASan) è uno strumento rapido basato sul compilatore per rilevare bug di memoria nel codice nativo.
ASan rileva:
- Overflow/underflow del buffer dello stack e dell'heap
- Utilizzo dell'heap dopo la liberazione
- Utilizzo dello stack al di fuori dell'ambito
- Liberazione doppia/liberazione non valida
ASan viene eseguito su ARM a 32 e 64 bit, oltre che su x86 e x86-64. Il sovraccarico della CPU di ASan è di circa 2 volte, il sovraccarico delle dimensioni del codice è compreso tra il 50% e 2 volte e il sovraccarico di memoria è elevato (dipende dai pattern di allocazione, ma è dell'ordine di 2 volte).
Android 10 e il ramo di release più recente di AOSP su AArch64 supportano Hardware-assisted AddressSanitizer (HWASan), uno strumento simile con un sovraccarico di RAM inferiore e una gamma più ampia di bug rilevati. Oltre ai bug rilevati da ASan, HWASan rileva l'utilizzo dello stack dopo il ritorno.
HWASan ha un sovraccarico di CPU e dimensioni del codice simile, ma un sovraccarico di RAM molto inferiore (15%). HWASan non è deterministico. Esistono solo 256 valori di tag possibili, quindi la probabilità di perdere un bug è fissa dello 0,4%. HWASan non ha le zone rosse di dimensioni limitate di ASan per rilevare gli overflow e la quarantena a capacità limitata per rilevare l'utilizzo dopo la liberazione, quindi non importa a HWASan quanto sia grande l'overflow o quanto tempo fa la memoria è stata deallocata. Questo rende HWASan migliore di ASan. Puoi scoprire di più sulla progettazione di HWASan o sull'utilizzo di HWASan su Android.
Oltre agli overflow dell'heap, ASan rileva gli overflow dello stack/globali ed è veloce con un sovraccarico di memoria minimo.
Questo documento descrive come creare ed eseguire parti/tutto Android con ASan. Se stai creando un'app SDK/NDK con ASan, consulta Address Sanitizer.
Sanificare i singoli eseguibili con ASan
Aggiungi LOCAL_SANITIZE:=address o sanitize: { address: true } alla regola di build per l'eseguibile. Puoi cercare nel codice esempi esistenti o trovare gli altri sanificatori disponibili.
Quando viene rilevato un bug, ASan stampa un report dettagliato sia nell'output standard sia in logcat, quindi arresta il processo.
Sanificare le librerie condivise con ASan
A causa del modo in cui funziona ASan, una libreria creata con ASan può essere utilizzata solo da un eseguibile creato con ASan.
Per sanificare una libreria condivisa utilizzata in più eseguibili, non tutti creati con ASan, sono necessarie due copie della libreria. Il modo consigliato per farlo è aggiungere quanto segue ad Android.mk per il modulo in questione:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
In questo modo, la libreria viene inserita in /system/lib/asan anziché
/system/lib. Quindi, esegui l'eseguibile con:
LD_LIBRARY_PATH=/system/lib/asan
Per i daemon di sistema, aggiungi quanto segue alla sezione appropriata di /init.rc o /init.$device$.rc.
setenv LD_LIBRARY_PATH /system/lib/asan
Verifica che il processo utilizzi le librerie da /system/lib/asan quando presenti leggendo /proc/$PID/maps. In caso contrario, potrebbe essere necessario disattivare SELinux:
adb rootadb shell setenforce 0# restart the process with adb shell kill $PID # if it is a system service, or may be adb shell stop; adb shell start.
Analisi dello stack migliori
ASan utilizza un unwinder rapido basato su frame pointer per registrare un'analisi dello stack per ogni evento di allocazione e deallocazione della memoria nel programma. La maggior parte di Android è creata senza frame pointer. Di conseguenza, spesso si ottengono solo uno o due frame significativi. Per risolvere il problema, ricompila la libreria con ASan (opzione consigliata) o con:
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
In alternativa, imposta ASAN_OPTIONS=fast_unwind_on_malloc=0 nell'ambiente del processo. Quest'ultima opzione può richiedere un utilizzo intensivo della CPU, a seconda del carico.
Simbolizzazione
Inizialmente, i report ASan contengono riferimenti agli offset nei file binari e nelle librerie condivise. Esistono due modi per ottenere informazioni sul file di origine e sulla riga:
- Assicurati che il file binario
llvm-symbolizersia presente in/system/bin.llvm-symbolizerviene creato dalle origini inthird_party/llvm/tools/llvm-symbolizer. - Filtra il report tramite lo script
external/compiler-rt/lib/asan/scripts/symbolize.py.
Il secondo approccio può fornire più dati (ovvero posizioni file:line) grazie alla disponibilità di librerie simbolizzate sull'host.
ASan nelle app
ASan non può vedere il codice Java, ma può rilevare bug nelle librerie JNI. Per farlo, devi creare l'eseguibile con ASan, che in
questo caso è /system/bin/app_process(32|64). In questo modo, ASan viene attivato contemporaneamente in tutte le app sul dispositivo, il che comporta un carico elevato, ma un dispositivo con 2 GB di RAM dovrebbe essere in grado di gestirlo.
Aggiungi LOCAL_SANITIZE:=address a
lla app_process regola di build in frameworks/base/cmds/app_process.
Modifica la sezione service zygote del file system/core/rootdir/init.zygote(32|64).rc appropriato per aggiungere le seguenti righe al blocco di righe con rientro contenente class main, con lo stesso rientro:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
setenv ASAN_OPTIONS allow_user_segv_handler=true
Crea, sincronizza con adb, esegui il flash di boot con fastboot e riavvia.
Utilizzare la proprietà wrap
L'approccio nella sezione precedente inserisce ASan in ogni app del sistema (in realtà, in ogni discendente del processo Zygote). È possibile eseguire solo una (o più) app con ASan, sacrificando un po' di sovraccarico di memoria per un avvio più lento dell'app.
Per farlo, avvia l'app con la proprietà wrap..
L'esempio seguente esegue l'app Gmail in ASan:
adb rootadb shell setenforce 0 # disable SELinuxadb shell setprop wrap.com.google.android.gm "asanwrapper"
In questo contesto, asanwrapper riscrive /system/bin/app_process in /system/bin/asan/app_process, che viene creato con ASan. Aggiunge anche /system/lib/asan all'inizio del percorso di ricerca della libreria dinamica. In questo modo, le librerie strumentate con ASan da /system/lib/asan hanno la precedenza sulle librerie normali
in /system/lib quando vengono eseguite con asanwrapper.
Se viene trovato un bug, l'app si arresta in modo anomalo e il report viene stampato nel log.
SANITIZE_TARGET
Android 7.0 e versioni successive includono il supporto per la creazione dell'intera piattaforma Android con ASan contemporaneamente. (Se stai creando una release successiva ad Android 9, HWASan è una scelta migliore.)
Esegui i seguenti comandi nella stessa struttura di build.
make -j42SANITIZE_TARGET=address make -j42
In questa modalità, userdata.img contiene librerie aggiuntive e deve essere eseguito il flash anche sul dispositivo. Utilizza la seguente riga di comando:
fastboot flash userdata && fastboot flashall
In questo modo vengono create due serie di librerie condivise: normali in /system/lib (la prima chiamata make) e strumentate con ASan in /data/asan/lib (la seconda chiamata make). Gli eseguibili della seconda build sovrascrivono quelli della prima build. Gli eseguibili strumentati con ASan ottengono un percorso di ricerca della libreria diverso che include /data/asan/lib prima di /system/lib tramite l'utilizzo di /system/bin/linker_asan in PT_INTERP.
Il sistema di compilazione sovrascrive le directory degli oggetti intermedi quando il valore $SANITIZE_TARGET è cambiato. In questo modo viene forzata una ricompilazione di tutti i target mantenendo i file binari installati in /system/lib.
Alcuni target non possono essere creati con ASan:
- Eseguibili collegati staticamente
- Target
LOCAL_CLANG:=false LOCAL_SANITIZE:=falsenon sono ASan'd perSANITIZE_TARGET=address
Gli eseguibili di questo tipo vengono ignorati nella build SANITIZE_TARGET e la versione della prima chiamata make viene lasciata in /system/bin.
Le librerie di questo tipo vengono create senza ASan. Possono contenere codice ASan dalle librerie statiche da cui dipendono.