AddressSanitizer (ASan) es una herramienta rápida basada en compiladores para detectar errores de memoria en código nativo.
ASan detecta:
- Desabastecimiento de búfer de pila
- Uso de pila después de liberación
- Uso de pila fuera del alcance
- Cierre doble o cierre wild
ASan se ejecuta en ARM de 32 y 64 bits, además de x86 y x86-64. La sobrecarga de la CPU de ASan es de aproximadamente el doble, el tamaño del código aumenta entre el 50% y el doble, y la sobrecarga de la memoria es considerable (depende de tus patrones de asignación, pero es de aproximadamente el doble).
Android 10 y la rama principal de AOSP en AArch64 admiten AddressSanitizer asistido por hardware (HWASan), una herramienta similar con menor sobrecarga de RAM y un rango más amplio de errores detectados. HWASan detecta el uso de pila después de la devolución, además de los errores que detecta ASan.
HWASan tiene una sobrecarga de CPU y tamaño de código similar, pero una sobrecarga de RAM mucho menor (15%). HWASan no es determinista. Solo hay 256 valores de etiqueta posibles, por lo que existe una probabilidad del 0.4% de que se pierda cualquier error. HWASan no tiene las zonas rojas de tamaño limitado de ASan para detectar desbordamientos ni la cuarentena de capacidad limitada para detectar el uso después de la liberación, por lo que no le importa a HWASan qué tan grande es el desbordamiento ni cuánto tiempo hace que se desasignó la memoria. Esto hace que HWASan sea mejor que ASan. Puedes obtener más información sobre el diseño de HWASan o sobre el uso de HWASan en Android.
ASan detecta desbordamientos de pila o globales, además de desbordamientos de montón, y es rápido con una sobrecarga de memoria mínima.
En este documento, se describe cómo compilar y ejecutar partes o todo Android con ASan. Si compilas una app de SDK o NDK con ASan, consulta Address Sanitizer.
Limpia ejecutables individuales con ASan
Agrega LOCAL_SANITIZE:=address
o sanitize: { address: true }
a la regla de compilación del ejecutable. Puedes buscar en el código ejemplos existentes o los otros desinfectantes disponibles.
Cuando se detecta un error, ASan imprime un informe detallado en la salida estándar y en logcat
y, luego, hace que el proceso falle.
Cómo sanear bibliotecas compartidas con ASan
Debido a la forma en que funciona ASan, una biblioteca compilada con ASan solo puede ser usada por un ejecutable compilado con ASan.
Para limpiar una biblioteca compartida que se usa en varios ejecutables, no todos los cuales se compilan con ASan, necesitas dos copias de la biblioteca. La forma recomendada de hacerlo es agregar lo siguiente a Android.mk
para el módulo en cuestión:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
Esto coloca la biblioteca en /system/lib/asan
en lugar de /system/lib
. Luego, ejecuta el archivo ejecutable con el siguiente comando:
LD_LIBRARY_PATH=/system/lib/asan
Para los daemons del sistema, agrega lo siguiente a la sección adecuada de /init.rc
o /init.$device$.rc
.
setenv LD_LIBRARY_PATH /system/lib/asan
Para verificar que el proceso use bibliotecas de /system/lib/asan
cuando esté presente, lee /proc/$PID/maps
. Si no es así, es posible que debas inhabilitar SELinux:
adb root
adb 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.
Mejores seguimientos de pila
ASan usa un desenredador rápido basado en punteros de marco para registrar un seguimiento de pila para cada evento de asignación y desasignación de memoria en el programa. La mayor parte de Android se compila sin punteros de marco. Como resultado, a menudo solo obtienes uno o dos fotogramas significativos. Para solucionar este problema, vuelve a compilar la biblioteca con ASan (recomendado) o con lo siguiente:
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
O bien, configura ASAN_OPTIONS=fast_unwind_on_malloc=0
en el entorno del proceso. Este último puede ser muy intensivo en la CPU, según la carga.
Simbolización
Inicialmente, los informes de ASan contienen referencias a desplazamientos en objetos binarios y bibliotecas compartidas. Hay dos maneras de obtener información del archivo fuente y de la línea:
- Asegúrate de que el objeto binario
llvm-symbolizer
esté presente en/system/bin
.llvm-symbolizer
se compila a partir de fuentes enthird_party/llvm/tools/llvm-symbolizer
. - Filtra el informe con la secuencia de comandos
external/compiler-rt/lib/asan/scripts/symbolize.py
.
El segundo enfoque puede proporcionar más datos (es decir, ubicaciones de file:line
) debido a la disponibilidad de bibliotecas simbolizadas en el host.
ASan en apps
ASan no puede ver el código Java, pero puede detectar errores en las bibliotecas de JNI. Para ello, debes compilar el ejecutable con ASan, que en este caso es /system/bin/app_process(32|64)
. Esto habilita ASan en todas las apps del dispositivo al mismo tiempo, lo que genera una carga pesada, pero un dispositivo con 2 GB de RAM debería poder controlar esto.
Agrega LOCAL_SANITIZE:=address
a la regla de compilación app_process
en frameworks/base/cmds/app_process
. Por ahora, ignora el objetivo app_process__asan
en el mismo archivo (si aún está allí cuando leas esto).
Edita la sección service zygote
del archivo system/core/rootdir/init.zygote(32|64).rc
correspondiente para agregar las siguientes líneas al bloque de líneas con sangría que contiene class main
, también con la misma sangría:
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib setenv ASAN_OPTIONS allow_user_segv_handler=true
Compila, sincroniza adb, inicia fastboot flash y reinicia.
Usa la propiedad wrap
El enfoque de la sección anterior coloca ASan en cada app del sistema (en realidad, en cada descendiente del proceso de Zygote). Es posible ejecutar solo una (o varias) apps con ASan, lo que implica un aumento de la sobrecarga de memoria para un inicio más lento de la app.
Para ello, inicia tu app con la propiedad wrap.
.
En el siguiente ejemplo, se ejecuta la app de Gmail en ASan:
adb root
adb shell setenforce 0 # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"
En este contexto, asanwrapper
reescribe /system/bin/app_process
a /system/bin/asan/app_process
, que se compila con ASan. También agrega /system/lib/asan
al comienzo de la ruta de búsqueda de la biblioteca dinámica. De esta manera, las bibliotecas instrumentadas por ASan de /system/lib/asan
se prefieren a las bibliotecas normales en /system/lib
cuando se ejecutan con asanwrapper
.
Si se encuentra un error, la app falla y el informe se imprime en el registro.
SANITIZE_TARGET
Android 7.0 y las versiones posteriores incluyen compatibilidad con la compilación de toda la plataforma de Android con ASan a la vez. (Si compilas una versión posterior a Android 9, HWASan es una mejor opción).
Ejecuta los siguientes comandos en el mismo árbol de compilación.
make -j42
SANITIZE_TARGET=address make -j42
En este modo, userdata.img
contiene bibliotecas adicionales y también se debe escribir en el dispositivo. Usa la siguiente línea de comandos:
fastboot flash userdata && fastboot flashall
Esto compila dos conjuntos de bibliotecas compartidas: normales en /system/lib
(la primera invocación de make) y con instrumentación de ASan en /data/asan/lib
(la segunda invocación de make). Los ejecutables de la segunda compilación reemplazan a los de la primera. Los ejecutables instrumentados por ASan obtienen una ruta de búsqueda de bibliotecas diferente que incluye /data/asan/lib
antes de /system/lib
a través del uso de /system/bin/linker_asan
en PT_INTERP
.
El sistema de compilación reemplaza los directorios de objetos intermedios cuando cambia el valor de $SANITIZE_TARGET
. Esto fuerza una nueva compilación de todos los objetivos mientras se conservan los ejecutables instalados en /system/lib
.
Algunos destinos no se pueden compilar con ASan:
- Archivos ejecutables vinculados de forma estática
LOCAL_CLANG:=false
objetivosLOCAL_SANITIZE:=false
no están ASan paraSANITIZE_TARGET=address
Los ejecutables como estos se omiten en la compilación de SANITIZE_TARGET
, y la versión de la primera invocación de make se deja en /system/bin
.
Las bibliotecas como esta se compilan sin ASan. Pueden contener algún código ASan de las bibliotecas estáticas de las que dependen.