Preparat do dezynfekcji adresu

Android NDK obsługuje Address Sanitizer (znany też jako ASan) od poziomu interfejsu API 27 (Android O MR1).

ASan to szybkie narzędzie oparte na kompilatorze, które służy do wykrywania błędów pamięci w kodzie natywnym. ASan wykrywa:

  • Przepełnienie lub niedopełnienie bufora stosu i sterty
  • Wykorzystanie sterty po zwolnieniu
  • Użycie stosu poza zakresem
  • Podwójny bezpłatny/dziki bezpłatny

Narzędzie ASan powoduje około 2-krotny narzut na procesor, 50–200% narzut na rozmiar kodu i duży narzut na pamięć (zależny od wzorców alokacji, ale rzędu 2x).

Przykładowa aplikacja

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

Kompilacja

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

ndk-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 przypadku każdego modułu 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"
            }
        }
    }
}

W przypadku każdego celu 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

Od Androida O MR1 (API w wersji 27) aplikacja może udostępniać skrypt powłoki opakowującej, który może opakowywać lub zastępować proces aplikacji. Umożliwia to aplikacji z możliwością debugowania dostosowywanie uruchamiania, co pozwala na używanie ASan na urządzeniach produkcyjnych.

  1. Dodaj android:debuggable do pliku manifestu aplikacji.
  2. W pliku build.gradle aplikacji ustaw wartość useLegacyPackaging na true. Więcej informacji znajdziesz w przewodniku dotyczącym skryptu powłoki opakowującej.
  3. Dodaj bibliotekę środowiska wykonawczego ASan do pliku jniLibs modułu aplikacji.
  4. Dodaj do każdego katalogu w katalogu src/main/resources/lib pliki wrap.sh z tą treś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
    "$@"
    

Jeśli moduł aplikacji projektu ma nazwę app, ostateczna struktura katalogu powinna zawierać te elementy:

<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

Address Sanitizer musi cofnąć stos przy każdym wywołaniu malloc/realloc/free. Masz 2 możliwości:

  1. „Szybki” mechanizm rozwijania stosu oparty na wskaźnikach ramki. Jest on używany zgodnie z instrukcjami w sekcji dotyczącej budowania.

  2. „Wolny” dekompilator CFI. W tym trybie ASan używa _Unwind_Backtrace. Wymaga tylko -funwind-tables, która jest zwykle domyślnie włączona.

Szybkie rozwijanie stosu jest domyślnym ustawieniem w przypadku funkcji malloc/realloc/free. Powolne rozwijanie stosu jest domyślnym ustawieniem w przypadku zrzutów stosu błędów krytycznych. Powolne rozwijanie można włączyć dla wszystkich śladów stosu, dodając fast_unwind_on_malloc=0 do zmiennej ASAN_OPTIONS w pliku wrap.sh.