AddressSanitizer

AddressSanitizer (ASan) to szybkie narzędzie oparte na kompilatorze, które wykrywa błędy pamięci w kodzie natywnym.

ASan wykrywa:

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

ASan działa na 32-bitowych i 64-bitowych urządzeniach z procesorami ARM oraz na urządzeniach x86 i x86-64. 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).

Android 10 i najnowsza gałąź AOSP na AArch64 obsługują AddressSanitizer z pomocą sprzętową (HWASan), podobne narzędzie o mniejszym obciążeniu pamięci RAM i większym zakresie wykrywanych błędów. HWASan wykrywa użycie stosu po powrocie, a także błędy wykrywane przez ASan.

HWASan ma podobny narzut na procesor i rozmiar kodu, ale znacznie mniejszy narzut na pamięć RAM (15%). HWASan jest niedeterministyczny. Istnieje tylko 256 możliwych wartości tagów, więc prawdopodobieństwo pominięcia błędu wynosi 0,4%. HWASan nie ma stref czerwonych o ograniczonej wielkości, które są używane przez ASan do wykrywania przepełnień, ani kwarantanny o ograniczonej pojemności, która służy do wykrywania błędów typu use-after-free. Dlatego w przypadku HWASan nie ma znaczenia, jak duże jest przepełnienie ani jak dawno pamięć została zwolniona. Dzięki temu HWASan jest lepszy od ASan. Możesz dowiedzieć się więcej o architekturze HWASan lub o korzystaniu z HWASan na Androidzie.

ASan wykrywa przepełnienia stosu/globalne oprócz przepełnień sterty i jest szybki przy minimalnym narzucie pamięci.

W tym dokumencie opisujemy, jak skompilować i uruchomić części lub całość Androida z ASan. Jeśli tworzysz aplikację SDK/NDK za pomocą ASan, zamiast tego przeczytaj artykuł Address Sanitizer.

Czyszczenie poszczególnych plików wykonywalnych za pomocą ASan

Dodaj LOCAL_SANITIZE:=address lub sanitize: { address: true } do reguły kompilacji pliku wykonywalnego. Możesz wyszukać w kodzie istniejące przykłady lub znaleźć inne dostępne funkcje oczyszczania.

Gdy ASan wykryje błąd, wyświetla szczegółowy raport na standardowym wyjściu i w logcat, a następnie powoduje awarię procesu.

Sanityzacja bibliotek udostępnionych za pomocą ASan

Ze względu na sposób działania ASan biblioteki utworzonej za pomocą ASan może używać tylko plik wykonywalny utworzony za pomocą ASan.

Aby oczyścić bibliotekę współużytkowaną używaną w wielu plikach wykonywalnych, z których nie wszystkie są tworzone za pomocą ASan, potrzebujesz 2 kopii biblioteki. Zalecany sposób to dodanie do pliku Android.mk odpowiedniego modułu tego kodu:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

Spowoduje to umieszczenie biblioteki w /system/lib/asan zamiast w /system/lib. Następnie uruchom plik wykonywalny za pomocą tego polecenia:

LD_LIBRARY_PATH=/system/lib/asan

W przypadku demonów systemowych dodaj te informacje do odpowiedniej sekcji pliku /init.rc lub /init.$device$.rc.

setenv LD_LIBRARY_PATH /system/lib/asan

Sprawdź, czy proces korzysta z bibliotek z /system/lib/asan w przypadku ich obecności, czytając /proc/$PID/maps. Jeśli nie, może być konieczne wyłączenie 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.

Lepsze śledzenie stosu

ASan używa szybkiego, opartego na wskaźniku ramki mechanizmu rozwijania stosu do rejestrowania śladu stosu dla każdego zdarzenia przydzielenia i zwolnienia pamięci w programie. Większość Androida jest zbudowana bez wskaźników ramki. W rezultacie często otrzymujesz tylko 1–2 znaczące klatki. Aby to naprawić, ponownie skompiluj bibliotekę za pomocą ASan (zalecane!) lub za pomocą:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

Możesz też ustawić ASAN_OPTIONS=fast_unwind_on_malloc=0 w środowisku procesu. To drugie może być bardzo obciążające dla procesora, w zależności od obciążenia.

Symbolizacja

Początkowo raporty ASan zawierają odniesienia do przesunięć w plikach binarnych i bibliotekach współdzielonych. Informacje o pliku źródłowym i wierszu można uzyskać na 2 sposoby:

  • Sprawdź, czy plik binarny llvm-symbolizer znajduje się w lokalizacji /system/bin. llvm-symbolizer jest tworzony na podstawie źródeł w third_party/llvm/tools/llvm-symbolizer.
  • Przefiltruj raport za pomocą external/compiler-rt/lib/asan/scripts/symbolize.pyskryptu.

Drugie podejście może dostarczać więcej danych (czyli file:linelokalizacji) ze względu na dostępność bibliotek symboli na hoście.

ASan w aplikacjach

ASan nie widzi kodu Java, ale może wykrywać błędy w bibliotekach JNI. W tym celu musisz skompilować plik wykonywalny za pomocą ASan, czyli w tym przypadku /system/bin/app_process(32|64). Umożliwia to włączenie ASan we wszystkich aplikacjach na urządzeniu jednocześnie, co stanowi duże obciążenie, ale urządzenie z 2 GB pamięci RAM powinno sobie z tym poradzić.

Dodaj LOCAL_SANITIZE:=address do reguły kompilacji app_process w frameworks/base/cmds/app_process.

W sekcji service zygote odpowiedniego pliku system/core/rootdir/init.zygote(32|64).rc dodaj te wiersze do bloku wciętych wierszy zawierających class main, również wciętych o tę samą wartość:

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

Kompilacja, synchronizacja za pomocą adb, flashowanie za pomocą fastboot i ponowne uruchomienie.

Używanie właściwości wrap

Podejście opisane w poprzedniej sekcji powoduje umieszczenie ASan w każdej aplikacji w systemie (a właściwie w każdym procesie potomnym procesu Zygote). Możesz uruchomić tylko jedną (lub kilka) aplikacji z ASan, poświęcając trochę pamięci na wolniejsze uruchamianie aplikacji.

Możesz to zrobić, zaczynając od właściwości wrap.. W przykładzie poniżej aplikacja Gmail jest uruchamiana w ASan:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

W tym kontekście asanwrapper przepisuje /system/bin/app_process na /system/bin/asan/app_process, które jest zbudowane za pomocą ASan. Dodaje też /system/lib/asan na początku ścieżki wyszukiwania biblioteki dynamicznej. Dzięki temu biblioteki z instrumentacją ASan z /system/lib/asan są preferowane od zwykłych bibliotek z /system/lib podczas uruchamiania z asanwrapper.

Jeśli zostanie znaleziony błąd, aplikacja ulegnie awarii, a raport zostanie wydrukowany w dzienniku.

SANITIZE_TARGET

Android 7.0 i nowsze wersje obsługują tworzenie całej platformy Androida za pomocą ASan. (Jeśli tworzysz wersję wyższą niż Android 9, lepszym wyborem będzie HWASan).

Uruchom te polecenia w tym samym drzewie kompilacji.

make -j42
SANITIZE_TARGET=address make -j42

W tym trybie userdata.img zawiera dodatkowe biblioteki i musi być również wgrany na urządzenie. Użyj tego wiersza poleceń:

fastboot flash userdata && fastboot flashall

Spowoduje to utworzenie 2 zestawów bibliotek współdzielonych: normalnego w /system/lib (pierwsze wywołanie make) i instrumentowanego przez ASan w /data/asan/lib (drugie wywołanie make). Pliki wykonywalne z drugiej kompilacji zastępują pliki z pierwszej kompilacji. Wykonywalne pliki z instrumentacją ASan mają inną ścieżkę wyszukiwania bibliotek, która zawiera /data/asan/lib przed /system/lib dzięki użyciu /system/bin/linker_asanPT_INTERP.

System kompilacji usuwa katalogi obiektów pośrednich, gdy zmieni się wartość $SANITIZE_TARGET. Wymusza to ponowne skompilowanie wszystkich elementów docelowych przy zachowaniu zainstalowanych plików binarnych w /system/lib.

Niektórych elementów docelowych nie można utworzyć za pomocą ASan:

  • Statycznie połączone pliki wykonywalne
  • Liczba celów: LOCAL_CLANG:=false
  • LOCAL_SANITIZE:=false nie są sprawdzane przez ASan w przypadku SANITIZE_TARGET=address

Pliki wykonywalne tego typu są pomijane w kompilacji SANITIZE_TARGET, a wersja z pierwszego wywołania polecenia make pozostaje w /system/bin.

Takie biblioteki są tworzone bez ASan. Mogą one zawierać kod ASan z zależnych od nich bibliotek statycznych.

Dokumenty pomocnicze