Dlaczego MTE?
błędy związane z bezpieczeństwem pamięci, czyli błędy w obsłudze pamięci w programowaniu natywnym; języki to typowe problemy z kodem. Zawierają luki w zabezpieczeniach, problemy ze stabilnością.
Firma Armv9 wprowadziła rozszerzenie MTE (Arm Memory Tagging Extension, MTE), które pozwala wychwytywać błędy „use-after-free” i „buffer-overflow”, do kodu natywnego.
Sprawdź pomoc
Od Androida 13 wybrane urządzenia obsługują MTE. Aby sprawdzić, czy Twoje urządzenie działa z włączoną obsługą MTE, uruchom następujące polecenie: polecenie:
adb shell grep mte /proc/cpuinfo
Jeśli wynik to Features : [...] mte
, urządzenie działa z przepisami MTE
.
Niektóre urządzenia nie włączają domyślnie MTE, ale umożliwiają deweloperom ponowne uruchomienie Włączono MTE. To konfiguracja eksperymentalna, która nie jest zalecana dla podczas normalnego użytkowania, ponieważ może to zmniejszyć wydajność lub stabilność urządzenia, ale może przydatne podczas tworzenia aplikacji. Aby z niego skorzystać, wybierz kolejno Opcje programisty > Rozszerzenie do tagowania pamięci masowej w aplikacji Ustawienia. Jeśli nie ma tej opcji, Twoje urządzenie nie pozwala na włączenie MTE w ten sposób.
Tryby działania MTE
MTE obsługuje 2 tryby: SYNC i ASYNC. Tryb SYNCHRONIZACJA zapewnia lepsze diagnozy dlatego lepiej nadają się do programowania. ma wysoką wydajność, co pozwala na włączenie go w opublikowanych aplikacjach.
Tryb synchroniczny (SYNC)
Ten tryb jest zoptymalizowany pod kątem debugowania, a nie wydajności, i może może służyć jako precyzyjne narzędzie do wykrywania błędów, gdy nakład pracy wymaga dużej wydajności, jest akceptowalna. Gdy ta opcja jest włączona, MTE SYNC działa również jako środek łagodzący bezpieczeństwo.
W przypadku niezgodności tagów procesor kończy proces przy nieprawidłowym wczytaniu lub instrukcje sklepu z SIGSEGV (z si_code SEGV_MTESERR) i pełnymi informacjami o dostępie do pamięci i adresie błędów.
Ten tryb przydaje się podczas testowania jako szybsza alternatywa dla HWASan, która nie wymaga ponownego kompilowania kodu ani w środowisku produkcyjnym, gdy aplikacja jest narażony na atak. Dodatkowo, gdy tryb ASYNC (opisany poniżej) wykryje dokładny raport o błędzie można uzyskać, przełączając się przez interfejs API środowiska wykonawczego w trybie SYNCHRONIZACJA.
Ponadto w trybie SYNCHRONIZator Androida zapisuje śledzenia stosu wszystkich alokacji i lokalizacji umów oraz wykorzystuje je do raporty o błędach, które zawierają wyjaśnienie błędów pamięci, np. „use-after-free” lub „buffer-overflow” oraz zrzuty stosu odpowiedniej pamięci zdarzeń (więcej informacji znajdziesz w artykule Omówienie raportów MTE). Taka dostarczają dodatkowych informacji kontekstowych i ułatwiają wykrywanie błędów niż w trybie ASYNC.
Tryb asynchroniczny (ASYNC)
Ten tryb jest zoptymalizowany pod kątem wydajności, głównie ze względu na dokładność raportów o błędach, i może być służy do szybkiego wykrywania błędów dotyczących bezpieczeństwa pamięci. W przypadku niezgodności tagów parametr procesor będzie kontynuował wykonywanie zadania do momentu najbliższego wpisu jądra (takiego jak wywołanie Syscall) lub przerwania licznika czasu), gdzie kończy się proces za pomocą polecenia SIGSEGV (kodu SEGV_MTEAERR) bez rejestrowania adresu błędu lub dostępu do pamięci.
Ten tryb jest przydatny w eliminowaniu luk w zabezpieczeniach pamięci w w dobrze przetestowanych bazach kodu, w których gęstość błędów związanych z bezpieczeństwem pamięci jest jest niski, co jest osiągane przy użyciu trybu SYNCHRONIZACJA podczas testowania.
Włącz MTE
Jedno urządzenie
Na potrzeby eksperymentów można zmienić zgodność aplikacji, aby ustawić wartość domyślną
wartość atrybutu memtagMode
dla aplikacji, która nie określa
dowolna wartość w pliku manifestu (lub określa "default"
).
Znajdziesz je w sekcji System > Zaawansowane > Opcje programisty > Promująca aplikację
Zmiany zgodności w globalnym menu ustawień. Ustawiam NATIVE_MEMTAG_ASYNC
lub NATIVE_MEMTAG_SYNC
włącza MTE dla konkretnej aplikacji.
Można też ustawić tę wartość za pomocą polecenia am
w następujący sposób:
- W trybie SYNCHRONIZACJA:
$ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
- W trybie ASYNC:
$ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name
W Gradle
Możesz włączyć MTE dla wszystkich kompilacji debugowania w projekcie Gradle, umieszczając
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application android:memtagMode="sync" tools:replace="android:memtagMode"/>
</manifest>
na app/src/debug/AndroidManifest.xml
. Spowoduje to zastąpienie wartości z pliku manifestu
memtagMode
z synchronizacją kompilacji do debugowania.
Możesz też włączyć MTE dla wszystkich kompilacji o niestandardowym typie kompilacji. Do zrobienia
więc utwórz własny obiekt buildType i umieść w obiekcie
Kod XML w: app/src/<name of buildType>/AndroidManifest.xml
.
Na potrzeby plików APK na wszystkich obsługujących tę funkcję urządzeniach
MTE jest domyślnie wyłączone. Aplikacje, które chcą używać MTE,
aby to zrobić, ustaw android:memtagMode
na liście <application>
lub <process>
w tagu AndroidManifest.xml
.
android:memtagMode=(off|default|sync|async)
Gdy atrybut jest ustawiony w tagu <application>
, ma wpływ na wszystkie wykorzystywane procesy
przez aplikację i można ją zastąpić dla poszczególnych procesów, ustawiając
tag <process>
.
Tworzenie z użyciem instrumentacji
Włączenie MTE, jak wyjaśniliśmy wcześniej, pomaga wykrywać błędy uszkodzenia pamięci sterta natywna. Wykrywanie uszkodzenia pamięci w stosie, a także włączenie MTE dla aplikacji, kod należy ponownie skompilować z użyciem instrumentacji. utworzona aplikacja będzie działać tylko na urządzeniach obsługujących MTE.
Aby utworzyć natywny kod aplikacji (JNI) za pomocą MTE, wykonaj te czynności:
ndk-build
W pliku Application.mk
:
APP_CFLAGS := -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag
APP_LDFLAGS := -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag
CMake
W przypadku każdego elementu docelowego w pliku CMakeLists.txt:
target_compile_options(${TARGET} PUBLIC -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag)
target_link_options(${TARGET} PUBLIC -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag)
Uruchamianie aplikacji
Po włączeniu MTE możesz używać aplikacji i przetestować ją jak zwykle. Jeśli problem z bezpieczeństwem pamięci
gdy zostanie wykryty, aplikacja ulega awarii, tworząc podobny do tego tombstone (uwaga
SIGSEGV
z SEGV_MTESERR
w przypadku SYNC lub SEGV_MTEAERR
w przypadku ASYNC):
pid: 13935, tid: 13935, name: sanitizer-statu >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0 0000007cd94227cc x1 0000007cd94227cc x2 ffffffffffffffd0 x3 0000007fe81919c0
x4 0000007fe8191a10 x5 0000000000000004 x6 0000005400000051 x7 0000008700000021
x8 0800007ae92853a0 x9 0000000000000000 x10 0000007ae9285000 x11 0000000000000030
x12 000000000000000d x13 0000007cd941c858 x14 0000000000000054 x15 0000000000000000
x16 0000007cd940c0c8 x17 0000007cd93a1030 x18 0000007cdcac6000 x19 0000007fe8191c78
x20 0000005800eee5c4 x21 0000007fe8191c90 x22 0000000000000002 x23 0000000000000000
x24 0000000000000000 x25 0000000000000000 x26 0000000000000000 x27 0000000000000000
x28 0000000000000000 x29 0000007fe8191b70
lr 0000005800eee0bc sp 0000007fe8191b60 pc 0000005800eee0c0 pst 0000000060001000
backtrace:
#00 pc 00000000000010c0 /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#01 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#02 pc 00000000000019cc /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000487d8 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
deallocated by thread 13935:
#00 pc 000000000004643c /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 00000000000421e4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 00000000000010b8 /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#03 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
allocated by thread 13935:
#00 pc 0000000000042020 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#01 pc 0000000000042394 /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#02 pc 000000000003cc9c /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
#03 pc 00000000000010ac /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
#04 pc 00000000000014a4 /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report
Więcej informacji znajdziesz w sekcji Informacje o raportach MTE w dokumentacji AOSP. Ty może też debugować aplikację przy użyciu Android Studio. Debuger zatrzymuje się na powodujący nieprawidłowy dostęp do pamięci.
Zaawansowani użytkownicy: używanie MTE we własnym przydzielaniu
Aby używać MTE w przypadku pamięci, która nie została przydzielona przez zwykłe przydziały systemu, musisz zmodyfikować lokalizator, aby oznaczać tagami pamięć i wskaźniki.
Strony przypisujące muszą zostać przydzielone za pomocą parametru PROT_MTE
w
Flaga prot
wartości mmap
(lub mprotect
).
Wszystkie otagowane przydziały muszą mieć wyrównanie 16 bajtów, ponieważ można przypisywać tylko tagi dla fragmentów 16-bajtowych (nazywanych też granułami).
Zanim zwrócisz wskaźnik, musisz użyć instrukcji IRG
, aby
wygenerować losowy tag i zapisać go we wskaźniku.
Aby otagować bazową pamięć, wykonaj te czynności:
STG
: dodaj tagi do pojedynczej 16-bajtowej granusyST2G
: dodaj tagi do 2 16-bajtowych granulekDC GVA
: wiersz z pamięci podręcznej tagu z tym samym tagiem
Możesz też wykonać te instrukcje, aby nie inicjować pamięci do zera:
STZG
: dodawanie tagów i inicjowanie zerowe dla pojedynczej 16-bajtowej granusySTZ2G
: dodawanie tagów i inicjowanie bez żadnych 2 16-bajtowych granulekDC GZVA
: tag i brak inicjowania pamięci podręcznej tym samym tagiem
Te instrukcje nie są obsługiwane na starszych procesorach, więc musisz wykonać te czynności: uruchamiać je warunkowo, gdy włączone jest MTE. Możesz sprawdzić, czy MTE jest włączone dla Twojego procesu:
#include <sys/prctl.h>
bool runningWithMte() {
int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
return mode != -1 && mode & PR_MTE_TCF_MASK;
}
Jako materiał referencyjny możesz użyć implementacji scudo.
Więcej informacji
Więcej informacji znajdziesz w Przewodniku MTE dla systemu operacyjnego Android przygotowanym przez Arm.