Rozszerzenie do tagowania pamięci RAM

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 flagi SA_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 .
  • 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.

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.