AddressSanitizer (ASan) est un outil rapide basé sur un compilateur qui permet de détecter les bugs liés à la mémoire dans le code natif.
ASan détecte les bugs suivants :
- Débordement positif/négatif de la pile et du tampon de tas de mémoire
- Bugs "use-after-free" au niveau des tas de mémoire
- Utilisation de la pile en dehors du champ d'application
- Bugs de type "double free"/"wild free"
ASan fonctionne sur les versions ARM 32 bits et 64 bits, ainsi que sur x86 et x86-64. La surcharge processeur d'ASan est environ deux fois plus importante, la surcharge de la taille du code est comprise entre 50% et deux fois supérieure, et la surcharge de la mémoire importante (en fonction de vos modèles d'allocation, mais de l'ordre de deux fois supérieure).
Android 10 et la branche principale de l'AOSP sur AArch64 sont compatibles avec HWASan (Hardware-assisted AddressSanitizer), un outil similaire avec une surcharge de RAM plus faible et une plage de bugs détectés plus large. HWASan détecte l'utilisation de la pile après le retour, en plus des bugs détectés par ASan.
HWASan présente une surcharge processeur et de taille de code similaire, mais une surcharge RAM beaucoup plus faible (15 %). HWASan est non déterministe. Il n'y a que 256 valeurs de balise possibles. Il y a donc une probabilité de 0,4 % de ne pas détecter un bug. HWASan ne dispose pas de zones rouges de taille limitée ASan pour la détection des dépassements ni d'une zone de quarantaine de capacité limitée pour détecter l'utilisation après libération. HWASan n'a donc pas d'importance pour la taille du débordement ou la durée de la désallocation de la mémoire. HWASan est donc plus performant qu'ASAN. Pour en savoir plus sur la conception de HWASan ou sur l'utilisation de HWASan sur Android, consultez les ressources suivantes.
ASan détecte les dépassements de pile/globaux en plus des dépassements de tas de mémoire, et est rapide avec un coût supplémentaire en mémoire minimal.
Ce document explique comment compiler et exécuter des parties ou l'intégralité d'Android avec ASan. Si vous créez une application SDK/NDK avec ASan, consultez plutôt Address Sanitizer.
Sanitiser des exécutables individuels avec ASan
Ajoutez LOCAL_SANITIZE:=address
ou sanitize: { address: true }
à la règle de compilation de l'exécutable. Vous pouvez rechercher des exemples existants dans le code ou trouver les autres nettoyeurs disponibles.
Lorsqu'un bug est détecté, ASan imprime un rapport détaillé à la fois dans la sortie standard et dans logcat
, puis plante le processus.
Sanitiser les bibliothèques partagées avec ASan
En raison du fonctionnement d'ASan, une bibliothèque créée avec ASan ne peut être utilisée que par un exécutable créé avec ASan.
Pour nettoyer une bibliothèque partagée utilisée dans plusieurs exécutables, dont tous ne sont pas compilés avec ASan, vous avez besoin de deux copies de la bibliothèque. La méthode recommandée consiste à ajouter les éléments suivants à Android.mk
pour le module en question:
LOCAL_SANITIZE:=address LOCAL_MODULE_RELATIVE_PATH := asan
La bibliothèque est alors placée dans /system/lib/asan
au lieu de /system/lib
. Exécutez ensuite votre exécutable avec :
LD_LIBRARY_PATH=/system/lib/asan
Pour les daemons système, ajoutez les éléments suivants à la section appropriée de /init.rc
ou /init.$device$.rc
.
setenv LD_LIBRARY_PATH /system/lib/asan
Vérifiez que le processus utilise des bibliothèques de /system/lib/asan
lorsqu'elles sont présentes en lisant /proc/$PID/maps
. Si ce n'est pas le cas, vous devrez peut-être désactiver 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.
Meilleures traces de la pile
ASan utilise un dérouleur rapide basé sur un pointeur de frame pour enregistrer une trace de pile pour chaque événement d'allocation et de désallocation de mémoire dans le programme. La plupart des systèmes Android sont conçus sans pointeurs de frame. Par conséquent, vous n'obtenez souvent qu'un ou deux frames significatifs. Pour résoudre ce problème, recompilez la bibliothèque avec ASan (recommandé) ou avec :
LOCAL_CFLAGS:=-fno-omit-frame-pointer LOCAL_ARM_MODE:=arm
Vous pouvez également définir ASAN_OPTIONS=fast_unwind_on_malloc=0
dans l'environnement de processus. Ce dernier peut être très gourmand en ressources processeur, en fonction de la charge.
Symbolisation
Au départ, les rapports ASan contiennent des références aux décalages dans les binaires et les bibliothèques partagées. Il existe deux façons d'obtenir des informations sur le fichier source et la ligne:
- Assurez-vous que le binaire
llvm-symbolizer
est présent dans/system/bin
.llvm-symbolizer
est créé à partir de sources dansthird_party/llvm/tools/llvm-symbolizer
. - Filtrez le rapport à l'aide du script
external/compiler-rt/lib/asan/scripts/symbolize.py
.
La deuxième approche peut fournir plus de données (c'est-à-dire des emplacements file:line
) en raison de la disponibilité de bibliothèques symbolisées sur l'hôte.
ASan dans les applications
ASan ne peut pas voir le code Java, mais il peut détecter les bugs dans les bibliothèques JNI. Pour ce faire, vous devez compiler l'exécutable avec ASan, qui est /system/bin/app_process(32|64)
dans ce cas. Cela active ASan dans toutes les applications de l'appareil en même temps, ce qui représente une charge importante, mais un appareil avec 2 Go de RAM devrait pouvoir gérer cela.
Ajoutez LOCAL_SANITIZE:=address
à la règle de compilation app_process
dans frameworks/base/cmds/app_process
. Ignorez la cible app_process__asan
dans le même fichier pour le moment (si elle est toujours là au moment où vous lisez ces lignes).
Modifiez la section service zygote
du fichier system/core/rootdir/init.zygote(32|64).rc
approprié pour ajouter les lignes suivantes au bloc de lignes indentées contenant class main
, également indentées de la même quantité :
setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib setenv ASAN_OPTIONS allow_user_segv_handler=true
Compilation, synchronisation adb, démarrage du flash Fastboot et redémarrage.
Utiliser la propriété wrap
L'approche de la section précédente place ASan dans chaque application du système (en réalité, dans chaque descendant du processus Zygote). Il n'est possible d'exécuter qu'une (ou plusieurs) application avec ASan, en échange d'une surcharge de mémoire pour un démarrage de l'application plus lent.
Pour ce faire, démarrez votre application avec la propriété wrap.
.
L'exemple suivant permet d'exécuter l'application Gmail sous ASan:
adb root
adb shell setenforce 0 # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"
Dans ce contexte, asanwrapper
réécrit /system/bin/app_process
en /system/bin/asan/app_process
, qui est compilé avec ASan. Il ajoute également /system/lib/asan
au début du chemin de recherche de la bibliothèque dynamique. De cette manière, les bibliothèques instrumentées par ASan de /system/lib/asan
sont préférées aux bibliothèques normales dans /system/lib
lors de l'exécution avec asanwrapper
.
Si un bug est détecté, l'application plante et le rapport est imprimé dans le journal.
SANITIZE_TARGET
Android 7.0 et versions ultérieures permettent de compiler l'intégralité de la plate-forme Android avec ASan en une seule fois. (Si vous créez une version supérieure à Android 9, HWASan est un meilleur choix.)
Exécutez les commandes suivantes dans la même arborescence de compilation.
make -j42
SANITIZE_TARGET=address make -j42
Dans ce mode, userdata.img
contient des bibliothèques supplémentaires et doit également être flashé sur l'appareil. Exécutez la ligne de commande suivante :
fastboot flash userdata && fastboot flashall
Cela crée deux ensembles de bibliothèques partagées : normales dans /system/lib
(première invocation de make) et instrumentées avec ASan dans /data/asan/lib
(deuxième invocation de make). Les exécutables du deuxième build écrasent ceux du premier. Les exécutables instrumentés par ASan obtiennent un chemin de recherche de bibliothèque différent qui inclut /data/asan/lib
avant /system/lib
via l'utilisation de /system/bin/linker_asan
dans PT_INTERP
.
Le système de compilation écrase les répertoires d'objets intermédiaires lorsque la valeur $SANITIZE_TARGET
a changé. Cela force une recompilation de toutes les cibles tout en préservant les binaires installés sous /system/lib
.
Certaines cibles ne peuvent pas être compilées avec ASan:
- Exécutables liés de manière statique
LOCAL_CLANG:=false
ciblesLOCAL_SANITIZE:=false
ne sont pas ASan pourSANITIZE_TARGET=address
Les exécutables de ce type sont ignorés dans la compilation SANITIZE_TARGET
, et la version de la première invocation de make est conservée dans /system/bin
.
Les bibliothèques de ce type sont construites sans ASan. Elles peuvent contenir du code ASan provenant des bibliothèques statiques dont elles dépendent.