Arm v9 wprowadza rozszerzenie Arm Memory Tagging Extension (MTE), sprzętową implementację oznaczonej pamięci.
Na wysokim poziomie MTE oznacza każdą alokację/delokację pamięci dodatkowymi metadanymi. Przypisuje znacznik do lokalizacji pamięci, która następnie może być powiązana ze wskaźnikami odwołującymi się do tej lokalizacji pamięci. W czasie wykonywania procesor sprawdza, czy wskaźnik i znaczniki metadanych są zgodne przy każdym ładowaniu i przechowywaniu.
W systemie Android 12 moduł alokacji pamięci jądra i sterty przestrzeni użytkownika może rozszerzyć każdą alokację o metadane. Pomaga to wykryć błędy związane z używaniem po zwolnieniu i przepełnieniem bufora, które są najczęstszym źródłem błędów związanych z bezpieczeństwem pamięci w naszych bazach kodów.
Tryby pracy MTE
MTE ma trzy tryby pracy:
- Tryb synchroniczny (SYNC)
- Tryb asynchroniczny (ASYNC)
- Tryb asymetryczny (ASYMM)
Tryb synchroniczny (SYNC)
Ten tryb jest zoptymalizowany pod kątem poprawności wykrywania błędów w stosunku do wydajności i może być używany jako precyzyjne narzędzie do wykrywania błędów, gdy akceptowalny jest większy narzut związany z wydajnością. Po włączeniu MTE SYNC działa jako środek ograniczający bezpieczeństwo. W przypadku niezgodności znaczników procesor natychmiast przerywa wykonywanie i kończy proces z SIGSEGV
(kod SEGV_MTESERR
) i pełną informacją o dostępie do pamięci i adresie powodującym błąd.
Zalecamy używanie tego trybu podczas testowania jako alternatywy dla HWASan/KASAN lub w środowisku produkcyjnym, gdy docelowy proces stanowi podatną powierzchnię ataku. Ponadto, gdy tryb ASYNC wskaże obecność błędu, dokładny raport o błędzie można uzyskać, korzystając z interfejsów API środowiska wykonawczego w celu przełączenia wykonywania w tryb SYNC.
Działając w trybie SYNC, alokator systemu Android rejestruje ślady stosu dla wszystkich alokacji i dezalokacji i wykorzystuje je do tworzenia lepszych raportów o błędach, które zawierają wyjaśnienie błędu pamięci, takiego jak użycie po zwolnieniu lub przepełnienie bufora, a także stosu. ślady odpowiednich zdarzeń pamięciowych. Takie raporty dostarczają więcej informacji kontekstowych i ułatwiają śledzenie i naprawianie błędów.
Tryb asynchroniczny (ASYNC)
Ten tryb jest zoptymalizowany pod kątem wydajności w stosunku do dokładności raportów o błędach i może być używany do wykrywania błędów związanych z bezpieczeństwem pamięci przy niskim narzucie.
W przypadku niezgodności znaczników procesor kontynuuje wykonywanie aż do najbliższego wpisu jądra (na przykład wywołania systemowego lub przerwania timera), gdzie kończy proces za pomocą SIGSEGV
(kod SEGV_MTEAERR
) bez zapisywania adresu powodującego błąd lub dostępu do pamięci.
Zalecamy używanie tego trybu w środowisku produkcyjnym na dobrze przetestowanych bazach kodu, o których wiadomo, że gęstość błędów bezpieczeństwa pamięci jest niska, co można osiągnąć poprzez użycie trybu SYNC podczas testowania.
Tryb asymetryczny (ASYMM)
Dodatkowa funkcja w Arm v8.7-A, tryb Asymmetric MTE, zapewnia synchroniczne sprawdzanie odczytów pamięci i asynchroniczne sprawdzanie zapisów w pamięci, z wydajnością podobną do trybu ASYNC. W większości sytuacji ten tryb stanowi ulepszenie w stosunku do trybu ASYNC i zalecamy używanie go zamiast ASYNC, gdy tylko 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 korzystał z trybu asymetrycznego, gdy żądany jest tryb asynchroniczny. Więcej informacji można znaleźć w sekcji „Konfigurowanie preferowanego poziomu MTE dla konkretnego procesora”.
MTE w przestrzeni użytkownika
W poniższych sekcjach opisano, w jaki sposób można włączyć MTE dla procesów systemowych i aplikacji. MTE jest domyślnie wyłączone, chyba że dla konkretnego procesu ustawiono jedną z poniższych opcji (zobacz poniżej , dla jakich komponentów MTE jest włączone).
Włączanie MTE przy użyciu systemu kompilacji
Jako właściwość obejmująca cały proces, MTE jest kontrolowane przez ustawienie czasu kompilacji głównego pliku wykonywalnego. Poniższe opcje umożliwiają zmianę tego ustawienia dla poszczególnych plików wykonywalnych lub całych podkatalogów w drzewie źródłowym. Ustawienie jest ignorowane w bibliotekach lub dowolnym obiekcie docelowym, który nie jest ani wykonywalny, ani testowy.
1. Włączenie MTE w Android.bp
( przykład ), dla konkretnego projektu:
Tryb MTE | Ustawienie |
---|---|
Asynchroniczny MTE | sanitize: { memtag_heap: true, } |
Synchroniczne MTE | sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, } |
lub w Android.mk:
Tryb MTE | Ustawienie |
---|---|
Asynchronous MTE | LOCAL_SANITIZE := memtag_heap |
Synchronous MTE | LOCAL_SANITIZE := memtag_heap LOCAL_SANITIZE_DIAG := memtag_heap |
2. Włączenie MTE w podkatalogu w drzewie źródłowym za pomocą zmiennej produktu:
Tryb MTE | Dołącz listę | Wyklucz listę |
---|---|---|
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 |
---|---|
Asynchroniczny MTE | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
Synchroniczne MTE | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
lub określając ścieżkę wykluczenia pliku wykonywalnego:
Tryb MTE | Ustawienie |
---|---|
Asynchroniczny MTE | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS |
Synchroniczne MTE |
Przykład (podobne użycie do PRODUCT_CFI_INCLUDE_PATHS
)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor) PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \ vendor/$(vendor)/projectB
Włączanie MTE przy użyciu właściwości systemu
Powyższe ustawienia kompilacji można zastąpić w czasie wykonywania, ustawiając następującą właściwość systemową:
arm64.memtag.process.<basename> = (off|sync|async)
Gdzie basename
oznacza podstawową nazwę pliku wykonywalnego.
Na przykład, aby ustawić /system/bin/ping
lub /data/local/tmp/ping
na użycie asynchronicznego MTE, użyj adb shell setprop arm64.memtag.process.ping async
.
Włączanie MTE przy użyciu zmiennej środowiskowej
Jeszcze jednym sposobem na zastąpienie ustawienia kompilacji jest zdefiniowanie zmiennej środowiskowej: MEMTAG_OPTIONS=(off|sync|async)
Jeśli zdefiniowano zarówno zmienną środowiskową, jak i właściwość systemową, zmienna ma pierwszeństwo.
Włączanie MTE dla aplikacji
Jeśli nie określono, MTE jest domyślnie wyłączone, ale aplikacje, które chcą używać MTE, mogą to zrobić, ustawiając android:memtagMode
w tagu <application>
lub <process>
w pliku AndroidManifest.xml
.
android:memtagMode=(off|default|sync|async)
Po ustawieniu na znaczniku <application>
atrybut wpływa na wszystkie procesy używane przez aplikację i można go zastąpić dla poszczególnych procesów, ustawiając znacznik <process>
.
Na potrzeby eksperymentów można zastosować zmiany zgodności w celu ustawienia domyślnej wartości atrybutu memtagMode
dla aplikacji, która nie określa żadnej wartości w manifeście (lub określa default
).
Można je znaleźć w obszarze System > Advanced > Developer options > App Compatibility Changes
w menu ustawień globalnych. Ustawienie NATIVE_MEMTAG_ASYNC
lub NATIVE_MEMTAG_SYNC
włącza MTE dla określonej aplikacji.
Alternatywnie można to ustawić za pomocą polecenia am
w następujący sposób:
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
Budowanie obrazu systemu MTE
Zdecydowanie zalecamy włączenie MTE we wszystkich natywnych plikach binarnych podczas programowania i wdrażania. Pomaga to wcześnie wykryć błędy związane z bezpieczeństwem pamięci i zapewnia realistyczny zasięg dla użytkowników, jeśli jest włączony w kompilacjach testowych.
Zdecydowanie zalecamy włączenie MTE w trybie synchronicznym we wszystkich natywnych plikach binarnych podczas programowania
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
Podobnie jak w przypadku każdej zmiennej w systemie kompilacji, SANITIZE_TARGET
może być używana jako zmienna środowiskowa lub ustawienie make
(na przykład w pliku product.mk
).
Należy pamiętać, że umożliwia to MTE dla wszystkich procesów natywnych, ale nie dla aplikacji (które są rozwidlone z zygote64
), dla których można włączyć MTE, postępując zgodnie z powyższymi instrukcjami.
Konfigurowanie preferowanego poziomu MTE specyficznego dla procesora
Na niektórych procesorach wydajność MTE w trybach ASYMM lub nawet SYNC może być podobna do wydajności ASYNC. To sprawia, że warto umożliwić bardziej rygorystyczne kontrole tych procesorów, gdy wymagany jest mniej rygorystyczny tryb sprawdzania, aby uzyskać korzyści w zakresie wykrywania błędów wynikające z bardziej rygorystycznych kontroli bez pogorszenia wydajności.
Domyślnie procesy skonfigurowane do działania w trybie ASYNC będą działać w trybie ASYNC na wszystkich procesorach. Aby skonfigurować jądro do uruchamiania tych procesów w trybie SYNC na określonych procesorach, wartość sync musi zostać zapisana we wpisie sysfs
/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
podczas rozruchu. Można to zrobić za pomocą skryptu inicjującego. Na przykład, aby skonfigurować procesory 0-1 do uruchamiania procesów w trybie ASYNC w trybie SYNC, a procesory 2-3 do uruchamiania w trybie ASYMM, do klauzuli init skryptu inicjującego dostawcy można dodać następujące elementy:
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
Nagrobki z procesów trybu ASYNC działających w trybie SYNC będą zawierać dokładny ślad stosu lokalizacji błędu pamięci. Nie będą one jednak zawierać śladu stosu alokacji ani dezalokacji. Te ślady 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
wynosi 0 lub 1.
Wyłącza inicjalizację pamięci w malloc i pozwala uniknąć zmiany znaczników pamięci, chyba że jest to konieczne dla poprawności.
int mallopt(M_MEMTAG_TUNING, level)
gdzie level
wynosi:
-
M_MEMTAG_TUNING_BUFFER_OVERFLOW
-
M_MEMTAG_TUNING_UAF
Wybiera strategię alokacji tagów.
- Ustawienie domyślne to
M_MEMTAG_TUNING_BUFFER_OVERFLOW
. -
M_MEMTAG_TUNING_BUFFER_OVERFLOW
- umożliwia deterministyczne wykrywanie błędów liniowego przepełnienia i niedomiaru bufora poprzez przypisanie odrębnych wartości znaczników do sąsiadujących alokacji. Ten tryb ma nieco zmniejszoną szansę na wykrycie błędów związanych z użyciem po zwolnieniu, ponieważ dla każdej lokalizacji pamięci dostępna jest tylko połowa możliwych wartości znaczników. Należy pamiętać, że MTE nie może wykryć przepełnienia w obrębie tej samej granulki znacznika (16-bajtowy fragment wyrównany) 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 jednej granulki nigdy nie jest wykorzystywana do wielokrotnych alokacji. -
M_MEMTAG_TUNING_UAF
- włącza niezależnie losowe tagi dla jednolitego ~93% prawdopodobieństwa wykrycia zarówno błędów przestrzennych (przepełnienie bufora), jak i czasowych (użyj po zwolnieniu).
Oprócz opisanych powyżej interfejsów API doświadczeni użytkownicy mogą chcieć wiedzieć o następujących kwestiach:
- Ustawienie rejestru sprzętowego
PSTATE.TCO
może tymczasowo uniemożliwić sprawdzanie znaczników ( przykład ). Na przykład podczas kopiowania zakresu pamięci z nieznaną zawartością znaczników lub rozwiązywania problemu wąskiego gardła wydajności w pętli gorącej. - Podczas korzystania z
M_HEAP_TAGGING_LEVEL_SYNC
systemowa procedura obsługi awarii zapewnia dodatkowe informacje, takie jak ślady stosu alokacji i dezalokacji. Ta funkcjonalność wymaga dostępu do bitów znacznika i jest włączana poprzez przekazanie flagiSA_EXPOSE_TAGBITS
podczas ustawiania procedury obsługi sygnału. Zaleca się, aby każdy program, który ustawia własną procedurę obsługi sygnału i delegował nieznane awarie do programu systemowego, zrobił to samo.
MTE w jądrze
Aby włączyć KASAN z akceleracją MTE dla jądra, skonfiguruj jądro z CONFIG_KASAN=y
, CONFIG_KASAN_HW_TAGS=y
. Te konfiguracje są domyślnie włączone w jądrach GKI, począwszy od Android 12-5.10
.
Można to kontrolować podczas uruchamiania systemu, używając następujących argumentów wiersza poleceń:
-
kasan=[on|off]
- włącz lub wyłącz KASAN (domyślnie:on
) -
kasan.mode=[sync |async ]
- wybierz pomiędzy trybem synchronicznym i asynchronicznym (domyślnie:sync
) -
kasan.stacktrace=[on|off]
– czy zbierać ślady stosu (domyślnie:on
)- Zbieranie śladów stosu wymaga również
stack_depot_disable=off
.
- Zbieranie śladów stosu wymaga również
-
kasan.fault=[report|panic]
- czy drukować tylko raport, czy też wywołać panikę w jądrze (domyślnie:report
). Niezależnie od tej opcji sprawdzanie tagów zostaje wyłączone po pierwszym zgłoszonym błędzie.
Zalecane użycie
Zdecydowanie zalecamy używanie trybu SYNC podczas wdrażania, programowania i testowania. Ta opcja powinna być włączona globalnie dla wszystkich procesów korzystających ze zmiennej środowiskowej lub z systemem kompilacji . W tym trybie błędy są wykrywane na wczesnym etapie procesu programowania, baza kodu jest szybciej stabilizowana i unika się kosztów wykrywania błędów w późniejszej fazie produkcji.
Zdecydowanie zalecamy używanie trybu ASYNC w środowisku produkcyjnym. Zapewnia to niedrogie narzędzie do wykrywania obecności błędów związanych z bezpieczeństwem pamięci w procesie, a także do dalszej dogłębnej obrony. Po wykryciu błędu programista może wykorzystać interfejsy API środowiska wykonawczego, aby przełączyć się w tryb SYNC i uzyskać dokładny ślad stosu od próbkowanej grupy użytkowników.
Zdecydowanie zalecamy skonfigurowanie preferowanego poziomu MTE specyficznego dla procesora dla SoC. Tryb Asymm ma zazwyczaj tę samą charakterystykę wydajności co tryb ASYNC i prawie zawsze jest od niego lepszy. Małe rdzenie w kolejności często wykazują podobną wydajność we wszystkich trzech trybach i można je skonfigurować tak, aby preferowały SYNC.
Programiści powinni sprawdzić obecność awarii, sprawdzając /data/tombstones
, logcat
lub monitorując potok DropboxManager
dostawcy pod kątem błędów użytkownika końcowego. Aby uzyskać więcej informacji na temat debugowania natywnego kodu Androida, zobacz informacje tutaj .
Komponenty platformy obsługujące MTE
W systemie Android 12 wiele komponentów systemu o krytycznym znaczeniu dla bezpieczeństwa korzysta z technologii MTE ASYNC do wykrywania awarii użytkowników końcowych i działania jako dodatkowa warstwa dogłębnej ochrony. Te komponenty to:
- Demony i narzędzia sieciowe (z wyjątkiem
netd
) - Bluetooth, SecureElement, NFC HAL i aplikacje systemowe
- demon
statsd
-
system_server
-
zygote64
(aby umożliwić aplikacjom wyrażenie zgody na korzystanie z MTE)
Cele te zostały wybrane w oparciu o następujące kryteria:
- Proces uprzywilejowany (definiowany jako proces, który ma dostęp do czegoś, czego nie ma domena unprivileged_app SELinux)
- Przetwarza niezaufane dane wejściowe ( Reguła dwóch )
- Akceptowalne spowolnienie wydajności (spowolnienie nie powoduje widocznych dla użytkownika opóźnień)
Zachęcamy dostawców do umożliwienia MTE produkcji większej liczby komponentów, zgodnie z kryteriami wymienionymi powyżej. Podczas opracowywania zalecamy testowanie tych komponentów w trybie SYNC, aby wykryć łatwe do naprawienia błędy i ocenić wpływ ASYNC na ich wydajność.
W przyszłości Android planuje rozszerzyć listę komponentów systemowych, na których jest włączone MTE, kierując się charakterystyką wydajnościową nadchodzących projektów sprzętu.