Rozszerzenie Arm Memory Tagging Extension (MTE)

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 granusy
  • ST2G: dodaj tagi do 2 16-bajtowych granulek
  • DC 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 granusy
  • STZ2G: dodawanie tagów i inicjowanie bez żadnych 2 16-bajtowych granulek
  • DC 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.