Address Sanitizer

Le NDK Android est compatible avec Address Sanitizer (également appelé ASan) à partir du niveau d'API 27 (Android O MR 1).

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"

La surcharge processeur d'ASan est plus ou moins multipliée par deux, la surcharge de la taille du code est comprise entre 50 % et deux fois supérieure, et la surcharge de la mémoire est élevée (en fonction de vos modèles d'allocation, mais de l'ordre de deux fois supérieure).

Application exemple

Une application exemple indique comment configurer une variante de compilation pour asan.

Compiler

Pour compiler le code natif (JNI) de votre application avec Address Sanitizer, procédez comme suit :

ndk-build

Dans votre fichier Application.mk :

APP_STL := c++_shared # Or system, or none.
APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
APP_LDFLAGS := -fsanitize=address

Pour chaque module de votre fichier Android.mk :

LOCAL_ARM_MODE := arm

CMake

Dans le fichier build.gradle de votre module :

android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                // Can also use system or none as ANDROID_STL.
                arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
            }
        }
    }
}

Pour chaque cible de votre fichier CMakeLists.txt :

target_compile_options(${TARGET} PUBLIC -fsanitize=address -fno-omit-frame-pointer)
set_target_properties(${TARGET} PROPERTIES LINK_FLAGS -fsanitize=address)

Exécuter

À partir d'Android O MR1 (niveau d'API 27), une application peut fournir un script shell qui peut encapsuler ou remplacer le processus de l'application. Une application débogable peut ainsi personnaliser le démarrage de son application, ce qui permet d'utiliser ASan sur des appareils de production.

  1. Ajoutez android:debuggable au fichier manifeste de l'application.
  2. Définissez useLegacyPackaging sur true dans le fichier build.gradle de votre application. Pour en savoir plus, consultez le guide sur l'encapsulation du script shell.
  3. Ajoutez la bibliothèque d'exécution ASan dans le répertoire jniLibs de votre module d'application.
  4. Ajoutez les fichiers wrap.sh avec le contenu suivant à chaque répertoire du répertoire src/main/resources/lib.

    #!/system/bin/sh
    HERE="$(cd "$(dirname "$0")" && pwd)"
    export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
    ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so)
    if [ -f "$HERE/libc++_shared.so" ]; then
        # Workaround for https://github.com/android-ndk/ndk/issues/988.
        export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
    else
        export LD_PRELOAD="$ASAN_LIB"
    fi
    "$@"
    

En supposant que le module d'application de votre projet s'appelle app, votre structure de répertoires finale doit inclure les éléments suivants :

<project root>
└── app
    └── src
        └── main
            ├── jniLibs
            │   ├── arm64-v8a
            │   │   └── libclang_rt.asan-aarch64-android.so
            │   ├── armeabi-v7a
            │   │   └── libclang_rt.asan-arm-android.so
            │   ├── x86
            │   │   └── libclang_rt.asan-i686-android.so
            │   └── x86_64
            │       └── libclang_rt.asan-x86_64-android.so
            └── resources
                └── lib
                    ├── arm64-v8a
                    │   └── wrap.sh
                    ├── armeabi-v7a
                    │   └── wrap.sh
                    ├── x86
                    │   └── wrap.sh
                    └── x86_64
                        └── wrap.sh

Traces de la pile

Address Sanitizer doit dérouler la pile à chaque appel malloc/realloc/free. Deux possibilités s'offrent à vous :

  1. Un dérouleur "rapide" basé sur un pointeur de frame. Pour cela, suivez les instructions de la section sur la compilation.

  2. Un dérouleur CFI "lent". Dans ce mode, ASan utilise _Unwind_Backtrace. Il ne nécessite que -funwind-tables, qui est normalement activé par défaut.

Le dérouleur rapide est l'option par défaut pour malloc/realloc/free. Le dérouleur lent est l'option par défaut pour les traces de pile fatales. Vous pouvez activer le dérouleur lent pour toutes les traces de la pile en ajoutant fast_unwind_on_malloc=0 à la variable ASAN_OPTIONS dans wrap.sh.