W architekturze Arm v9 wprowadzono rozszerzenie Arm Memory Tagging Extension (MTE), czyli sprzętową implementację pamięci z tagami.
W dużym uproszczeniu MTE taguje każdą alokację i dealokację pamięci dodatkowymi metadanymi. MTE przypisuje tag do lokalizacji pamięci, który można powiązać ze wskaźnikami odwołującymi się do tej lokalizacji. Podczas działania procesor sprawdza, czy wskaźnik i tagi metadanych pasują do siebie przy każdym wczytaniu i zapisie.
W Androidzie 12 alokator pamięci sterty jądra i przestrzeni użytkownika może uzupełniać każdą alokację o metadane. Pomaga to wykrywać błędy typu use-after-free i przepełnienia bufora, które są najczęstszym źródłem błędów bezpieczeństwa pamięci w naszych bazach kodu.
Tryby działania MTE
MTE ma 3 tryby działania:
- tryb synchroniczny (SYNC),
- tryb asynchroniczny (ASYNC),
- tryb asymetryczny (ASYMM).
Tryb synchroniczny (SYNC)
Ten tryb jest zoptymalizowany pod kątem dokładności wykrywania błędów, a nie wydajności.
Można go używać jako precyzyjnego narzędzia do wykrywania błędów, gdy dopuszczalny jest większy narzut na wydajność.
Gdy jest włączony, MTE SYNC działa jako zabezpieczenie.
W przypadku niezgodności tagów procesor natychmiast przerywa wykonywanie i
kończy proces za pomocą SIGSEGV (kod
SEGV_MTESERR) oraz pełnych informacji o dostępie do pamięci i
adresie błędu.
Zalecamy używanie tego trybu podczas testowania jako alternatywy dla HWASan/KASAN lub w środowisku produkcyjnym, gdy proces docelowy stanowi podatną na ataki powierzchnię. Ponadto, gdy tryb ASYNC wskaże obecność błędu, można uzyskać dokładny raport o błędzie, używając interfejsów API środowiska wykonawczego do przełączenia wykonywania na tryb SYNC.
Gdy działa w trybie SYNC, alokator Androida rejestruje zrzuty stosu dla wszystkich alokacji i dealokacji oraz używa ich do generowania lepszych raportów o błędach, które zawierają wyjaśnienie błędu pamięci, np. błędu typu use-after-free lub przepełnienia bufora, oraz zrzuty stosu odpowiednich zdarzeń pamięci. Takie raporty zawierają więcej informacji kontekstowych i ułatwiają śledzenie oraz naprawianie błędów.
Tryb asynchroniczny (ASYNC)
Ten tryb jest zoptymalizowany pod kątem wydajności, a nie dokładności raportów o błędach. Można go
używać do wykrywania błędów bezpieczeństwa pamięci z niskim narzutem.
W przypadku niezgodności tagów procesor kontynuuje wykonywanie aż do najbliższego
wejścia do jądra (np. wywołania systemowego lub przerwania timera), gdzie kończy
proces za pomocą SIGSEGV (kod SEGV_MTEAERR) bez
rejestrowania adresu błędu ani dostępu do pamięci.
Zalecamy używanie tego trybu w środowisku produkcyjnym w przypadku dobrze przetestowanych baz kodu, w których wiadomo, że występuje niewielka gęstość błędów bezpieczeństwa pamięci. Można to osiągnąć, używając trybu SYNC podczas testowania.
Tryb asymetryczny (ASYMM)
Dodatkowa funkcja w architekturze Arm v8.7-A, czyli asymetryczny tryb MTE, zapewnia synchroniczne sprawdzanie odczytów pamięci i asynchroniczne sprawdzanie zapisów pamięci. Jego wydajność jest podobna do wydajności trybu ASYNC. W większości sytuacji ten tryb jest lepszy od trybu ASYNC i zalecamy używanie go zamiast trybu ASYNC, gdy jest dostępny.
Z tego powodu żaden z opisanych poniżej interfejsów API nie wspomina o trybie asymetrycznym. Zamiast tego system operacyjny można skonfigurować tak, aby zawsze używał trybu asymetrycznego, gdy wymagany jest tryb asynchroniczny. Więcej informacji znajdziesz w artykule o konfigurowaniu preferowanego poziomu MTE dla danego procesora.
MTE w przestrzeni użytkownika
W sekcjach poniżej opisujemy, jak włączyć MTE w przypadku procesów systemowych i aplikacji. MTE jest domyślnie wyłączone, chyba że dla danego procesu ustawiono jedną z opcji poniżej (zobacz, w przypadku których komponentów MTE jest włączone poniżej).
Włączanie MTE za pomocą systemu kompilacji
Jako właściwość obejmująca cały proces MTE jest kontrolowane przez ustawienie głównego pliku wykonywalnego w czasie kompilacji. Poniższe opcje umożliwiają zmianę tego ustawienia w przypadku poszczególnych plików wykonywalnych lub całych podkatalogów w drzewie źródłowym. Ustawienie jest ignorowane w przypadku bibliotek lub dowolnego celu, który nie jest plikiem wykonywalnym ani a testem.
1. Włączanie MTE w pliku Android.bp (przykład)
w przypadku konkretnego projektu:
| Tryb MTE | Ustawienie |
|---|---|
| Asynchroniczne MTE | sanitize: {
memtag_heap: true,
} |
| Synchroniczne MTE | sanitize: {
memtag_heap: true,
diag: {
memtag_heap: true,
},
} |
lub w pliku Android.mk:
| Tryb MTE | Ustawienie |
|---|---|
Asynchronous MTE |
LOCAL_SANITIZE := memtag_heap |
Synchronous MTE |
LOCAL_SANITIZE := memtag_heapLOCAL_SANITIZE_DIAG := memtag_heap |
2. Włączanie MTE w podkatalogu w drzewie źródłowym za pomocą zmiennej produktu:
| Tryb MTE | Lista dołączeń | Lista wykluczeń |
|---|---|---|
| asynchroniczny | PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
| synchronizacja | PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS
MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
lub
| Tryb MTE | Ustawienie |
|---|---|
| Asynchroniczne MTE | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
| Synchroniczne MTE | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
lub przez określenie ścieżki wykluczenia pliku wykonywalnego:
| Tryb MTE | Ustawienie |
|---|---|
| Asynchroniczne MTE | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
| Synchroniczne MTE |
Przykład (podobny do użycia PRODUCT_CFI_INCLUDE_PATHS)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor)
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \
vendor/$(vendor)/projectBWłączanie MTE za pomocą właściwości systemu
Powyższe ustawienia kompilacji można zastąpić w czasie działania, ustawiając tę właściwość systemu:
arm64.memtag.process.<basename> = (off|sync|async)
Gdzie basename to nazwa podstawowa pliku wykonywalnego.
Aby na przykład ustawić /system/bin/ping lub /data/local/tmp/ping
na używanie asynchronicznego MTE, użyj polecenia adb shell setprop arm64.memtag.process.ping async.
Włączanie MTE za pomocą zmiennej środowiskowej
Innym sposobem na zastąpienie ustawienia kompilacji w przypadku procesów natywnych (nie aplikacji)
jest zdefiniowanie zmiennej środowiskowej: MEMTAG_OPTIONS=(off|sync|async)
Jeśli zdefiniowano zarówno zmienną środowiskową, jak i właściwość systemu, zmienna ma pierwszeństwo.
Włączanie MTE w aplikacjach
Jeśli nie określono inaczej, MTE jest domyślnie wyłączone, ale
aplikacje, które chcą korzystać z MTE, mogą to zrobić, ustawiając android:memtagMode
w tagu <application> lub
<process> w pliku
AndroidManifest.xml.
android:memtagMode=(off|default|sync|async)
Gdy ten atrybut jest ustawiony w tagu <application>, wpływa na wszystkie procesy używane przez aplikację. Można go zastąpić w przypadku poszczególnych procesów, ustawiając tag <process>.
Na potrzeby eksperymentowania można użyć zmian zgodności, aby ustawić domyślną wartość atrybutu memtagMode w przypadku aplikacji, która nie określa żadnej wartości w manifeście (lub określa wartość default).
Znajdują się one w menu ustawień globalnych w sekcji System > Zaawansowane > Opcje programisty
> Zmiany zgodności aplikacji. Ustawienie
NATIVE_MEMTAG_ASYNC lub NATIVE_MEMTAG_SYNC włącza MTE
w przypadku konkretnej aplikacji. Można to też zrobić za pomocą polecenia am
w ten sposób:
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
Kompilowanie obrazu systemu z włączonym MTE
Zdecydowanie zalecamy włączenie MTE we wszystkich binarnych plikach natywnych podczas tworzenia i uruchamiania. Pomaga to wykrywać błędy bezpieczeństwa pamięci na wczesnym etapie i zapewnia realistyczny zasięg użytkowników, jeśli jest włączone w kompilacjach testowych.
Zdecydowanie zalecamy włączenie MTE w trybie synchronicznym we wszystkich binarnych plikach natywnych podczas tworzenia.
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
Podobnie jak w przypadku każdej zmiennej w systemie kompilacji, SANITIZE_TARGET można
używać jako zmiennej środowiskowej lub ustawienia make (np. w
pliku product.mk).
Włącza to MTE we wszystkich procesach natywnych, ale nie w
aplikacjach (które są rozwidleniem procesu zygote64). W ich przypadku MTE można
włączyć, wykonując instrukcje opisane w sekcji Włączanie MTE w aplikacjach.
Konfigurowanie preferowanego poziomu MTE dla danego procesora
W przypadku niektórych procesorów wydajność MTE w trybie ASYMM, a nawet SYNC, może być podobna do wydajności trybu ASYNC. Dlatego warto włączyć bardziej rygorystyczne sprawdzanie na tych procesorach, gdy wymagany jest mniej rygorystyczny tryb sprawdzania. Pozwoli to uzyskać korzyści z wykrywania błędów przez bardziej rygorystyczne sprawdzanie bez negatywnego wpływu na wydajność.
Domyślnie procesy skonfigurowane do działania w trybie ASYNC działają w tym trybie na wszystkich procesorach. Aby skonfigurować jądro tak, aby uruchamiało te procesy w trybie SYNC na
określonych procesorach, należy zapisać wartość sync w
sysfs wpisie
/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred podczas uruchamiania
systemu. Można to zrobić za pomocą skryptu init. Aby na przykład skonfigurować procesory 0–1
tak, aby uruchamiały procesy w trybie ASYNC w trybie SYNC, a procesory 2–3 tak, aby działały w trybie ASYMM,
można dodać do klauzuli init skryptu init dostawcy te wiersze:
write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm
Tombstones z procesów w trybie ASYNC działających w trybie SYNC zawierają a dokładny zrzut stosu lokalizacji błędu pamięci. Nie zawierają jednak zrzutu stosu alokacji ani dealokacji. Te zrzuty stosu są dostępne tylko wtedy, gdy proces jest skonfigurowany do działania w trybie SYNC.
int mallopt(M_THREAD_DISABLE_MEM_INIT, level)
gdzie level to 0 lub 1.
Wyłącza inicjowanie pamięci w malloc i unika zmiany tagów pamięci chyba że jest to konieczne do prawidłowego działania.
int mallopt(M_MEMTAG_TUNING, level)
gdzie level to:
M_MEMTAG_TUNING_BUFFER_OVERFLOWM_MEMTAG_TUNING_UAF
Wybiera strategię alokacji tagów.
- Ustawienie domyślne to
M_MEMTAG_TUNING_BUFFER_OVERFLOW. M_MEMTAG_TUNING_BUFFER_OVERFLOWumożliwia deterministyczne wykrywanie liniowych błędów przepełnienia i niedopełnienia bufora przez przypisywanie różnych wartości tagów do sąsiednich alokacji. Ten tryb ma nieco mniejszą szansę na wykrycie błędów typu use-after-free, ponieważ dla każdej lokalizacji pamięci dostępna jest tylko połowa możliwych wartości tagów. MTE nie może wykryć przepełnienia w obrębie tego samego ziarna tagu (wyrównanego do 16 bajtów bloku) i może przeoczyć małe przepełnienia nawet w tym trybie. Takie przepełnienie nie może być przyczyną uszkodzenia pamięci, ponieważ pamięć w obrębie jednego ziarna nigdy nie jest używana do wielu alokacji.M_MEMTAG_TUNING_UAFumożliwia niezależne losowe tagi które zapewniają równomierne prawdopodobieństwo wykrycia zarówno błędów przestrzennych (przepełnienie bufora), jak i czasowych (use-after-free) na poziomie około 93%.
Oprócz opisanych powyżej interfejsów API doświadczeni użytkownicy mogą też zainteresować się tymi informacjami:
- Ustawienie rejestru sprzętowego
PSTATE.TCOmoże tymczasowo wyłączyć sprawdzanie tagów (przykład). Na przykład podczas kopiowania zakresu pamięci o nieznanej zawartości tagów lub rozwiązywania wąskiego gardła wydajności w pętli. - Gdy używasz
M_HEAP_TAGGING_LEVEL_SYNC, program obsługi awarii systemu udostępnia dodatkowe informacje, takie jak zrzuty stosu alokacji i dealokacji. Ta funkcja wymaga dostępu do bitów tagów i jest włączana przez przekazanie flagiSA_EXPOSE_TAGBITSpodczas ustawiania programu obsługi sygnałów. Każdy program, który ustawia własny program obsługi sygnałów i przekazuje nieznane awarie do programu systemowego, powinien postępować tak samo.
MTE w jądrze
Aby włączyć przyspieszany przez MTE KASAN w jądrze, skonfiguruj jądro za pomocą ustawień
CONFIG_KASAN=y, CONFIG_KASAN_HW_TAGS=y. Te konfiguracje
są domyślnie włączone w przypadku jąder GKI, począwszy od wersji Android
12-5.10.
Możesz to kontrolować podczas uruchamiania za pomocą tych argumentów wiersza poleceń:
kasan=[on|off]– włącza lub wyłącza KASAN (domyślnie:on)kasan.mode=[sync|async]– wybiera tryb synchroniczny lub asynchroniczny (domyślnie:sync)kasan.stacktrace=[on|off]– określa, czy zbierać zrzuty stosu (domyślnie:on)- zbieranie zrzutów stosu wymaga też ustawienia
stack_depot_disable=off.
- zbieranie zrzutów stosu wymaga też ustawienia
kasan.fault=[report|panic]– określa, czy tylko drukować raport, czy też powodować panikę jądra (domyślnie:report). Niezależnie od tej opcji sprawdzanie tagów jest wyłączane po pierwszym zgłoszonym błędzie.
Zalecane użycie
Zdecydowanie zalecamy używanie trybu SYNC podczas uruchamiania, tworzenia i testowania. Tę opcję należy włączyć globalnie w przypadku wszystkich procesów za pomocą zmiennej środowiskowej lub systemu kompilacji. W tym trybie błędy są wykrywane na wczesnym etapie procesu tworzenia, baza kodu jest szybciej stabilizowana, a koszty wykrywania błędów na późniejszym etapie produkcji są niższe.
Zdecydowanie zalecamy używanie trybu ASYNC w środowisku produkcyjnym. Zapewnia to narzędzie z niskim narzutem do wykrywania błędów bezpieczeństwa pamięci w procesie oraz dodatkową warstwę ochrony. Po wykryciu błędu deweloper może użyć interfejsów API środowiska wykonawczego, aby przełączyć się na tryb SYNC i uzyskać dokładny zrzut stosu z próbki użytkowników.
Zdecydowanie zalecamy skonfigurowanie preferowanego poziomu MTE dla danego procesora w przypadku SoC. Tryb ASYMM ma zwykle takie same parametry wydajności jak tryb ASYNC, i prawie zawsze jest od niego lepszy. Małe rdzenie in-order często wykazują podobną wydajność we wszystkich 3 trybach i można je skonfigurować tak, aby preferowały tryb SYNC.
Deweloperzy powinni sprawdzać występowanie awarii, sprawdzając
/data/tombstones,
logcat lub monitorując potok DropboxManager
dostawcy pod kątem błędów użytkowników. Więcej informacji o debugowaniu kodu natywnego Androida znajdziesz
informacje tutaj.
Komponenty platformy z włączonym MTE
W Androidzie 12 wiele krytycznych z punktu widzenia bezpieczeństwa komponentów systemu używa MTE ASYNC do wykrywania awarii użytkowników i jako dodatkowej warstwy ochrony. Te komponenty to:
- demony i narzędzia sieciowe (z wyjątkiem
netd) - Bluetooth, SecureElement, HAL NFC i aplikacje systemowe,
- demon
statsd, system_serverzygote64(aby umożliwić aplikacjom korzystanie z MTE).
Te cele zostały wybrane na podstawie tych kryteriów:
- proces z uprawnieniami (zdefiniowany jako proces, który ma dostęp do czegoś do czego nie ma dostępu domena SELinux unprivileged_app),
- procesy, które przetwarzają niezaufane dane wejściowe (zasada dwóch),
- dopuszczalne spowolnienie wydajności (spowolnienie nie powoduje opóźnienia widocznego dla użytkownika ).
Zachęcamy dostawców do włączania MTE w środowisku produkcyjnym w przypadku większej liczby komponentów, zgodnie z kryteriami wymienionymi powyżej. Podczas tworzenia zalecamy testowanie tych komponentów w trybie SYNC, aby wykrywać łatwe do naprawienia błędy i oceniać wpływ trybu ASYNC na ich wydajność.
Planujemy rozszerzyć listę komponentów systemu, w których włączono MTE , kierując się charakterystyką wydajności nadchodzących projektów sprzętowych.