Preparat do dezynfekcji adresu

NDK na Androida obsługuje narzędzie Address Sanitizer (znane też jako ASan) od poziomu API 27 (Android O MR 1).

ASan to szybkie narzędzie oparte na kompilatorach, które wykrywa błędy pamięci w kodzie natywnym. ASan wykrywa:

  • Przepełnienie lub niedostateczna ilość bufora stosu i buforu sterty
  • Wykorzystanie sterty po bezpłatnym
  • Użycie stosu poza zakresem
  • Podwójne bez ograniczeń

Obciążenie procesora w systemie ASan jest około 2 razy większe, narzut związany z rozmiarem kodu wynosi pomiędzy 50% a 2 razy. Ilość pamięci jest znaczna (zależy od wzorców alokacji, ale na poziomie 2x).

Przykładowa aplikacja

Przykładowa aplikacja pokazuje, jak skonfigurować wariant kompilacji dla Asana.

Budowanie

Aby utworzyć kod natywny (JNI) aplikacji za pomocą narzędzia Address Sanitizer, wykonaj te czynności:

NK Build

W pliku Application.mk:

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

W każdym module w pliku Android.mk:

LOCAL_ARM_MODE := arm

CMake

W pliku build.gradle modułu:

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

Dla każdego miejsca docelowego w pliku CMakeLists.txt:

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

Uruchom

Począwszy od Androida O O MR1 (poziom interfejsu API 27) aplikacja może udostępniać skrypt powłoki zawijania, który może spakować lub zastąpić proces aplikacji. Umożliwia to aplikacji z możliwością debugowania dostosowanie uruchamiania, co umożliwia używanie ASan na urządzeniach produkcyjnych.

  1. Dodaj android:debuggable do pliku manifestu aplikacji.
  2. Ustaw wartość useLegacyPackaging na true w pliku build.gradle aplikacji. Więcej informacji znajdziesz w przewodniku po skrypcie powłoki wrapowej.
  3. Dodaj bibliotekę środowiska wykonawczego ASan do pliku jniLibs modułu aplikacji.
  4. Do każdego katalogu w katalogu src/main/resources/lib dodaj pliki wrap.sh o poniższej zawartości.

    #!/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
    "$@"
    

Zakładając, że moduł aplikacji Twojego projektu nazywa się app, końcowa struktura katalogów powinna zawierać taką nazwę:

<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

Śledzenie stosu

Narzędzie Address Sanitizer musi wyłączać stos przy każdym wywołaniu malloc/realloc/free. Dostępne są 2 opcje:

  1. „Szybki” moduł rozwijania oparty na wskaźniku ramki. Jest on potrzebny, postępując zgodnie z instrukcjami w sekcji tworzenia.

  2. „Powolne” działanie CFI. W tym trybie ASan używa _Unwind_Backtrace. Wymaga jedynie uprawnienia -funwind-tables, które jest zwykle domyślnie włączone.

Szybkie Relaks to domyślna opcja w przypadku Malloc/realloc/free. Powolne rozwijanie to domyślny w przypadku krytycznych zrzutów stosu. Powolne rozwijanie funkcji można włączyć we wszystkich zrzutach stosu, dodając fast_unwind_on_malloc=0 do zmiennej ASAN_OPTIONS w pliku wrap.sh.