Android zaczyna wykrywać exploity zanim przejmą pamięć. Jak MTE zmienia bezpieczeństwo smartfonów
Android zaczyna wykrywać exploity zanim przejmą pamięć. Jak MTE zmienia bezpieczeństwo smartfonów
Przez lata ogromna część najbardziej niebezpiecznych podatności w Androidzie miała wspólny mianownik: błędy zarządzania pamięcią.
Buffer overflow.
Use-after-free.
Out-of-bounds access.
Heap corruption.
Dangling pointers.
W świecie C i C++ jeden pozornie niewinny błąd może doprowadzić do sytuacji, w której program zaczyna czytać albo zapisywać pamięć poza obszarem, do którego powinien mieć dostęp.
A wtedy zwykły crash może przestać być zwykłym crashem.
Może stać się początkiem exploita.
Android od lat dokłada kolejne warstwy obrony: sandbox aplikacji, SELinux, Verified Boot, ASLR, CFI, izolację procesów czy mechanizmy sprzętowe.
Ale pojawił się jeszcze jeden interesujący element:
Arm Memory Tagging Extension — MTE.
To nie jest kolejny filtr aplikacji.
To mechanizm działający na poziomie pamięci i sprzętu.
Jego zadanie jest proste:
wykryć sytuację, w której wskaźnik próbuje uzyskać dostęp do pamięci, do której nie powinien należeć.
I właśnie dlatego MTE może być jedną z ważniejszych technologii bezpieczeństwa Androida na urządzeniach Armv9.
Problem zaczyna się od pamięci
Wyobraźmy sobie klasyczny kod C:
char buffer[16];
memcpy(buffer, input, length);
Programista zakłada, że:
length <= 16
Jeżeli jednak dane wejściowe mają 100 bajtów, program może zacząć zapisywać poza granicą bufora.
Schemat:
BEZ BŁĘDU
[ buffer 16 B ]
[-------------]
BUFFER OVERFLOW
[ buffer 16 B ][ inne dane ][ inne dane ]
↑
└── nadpisywane
Jeżeli atakujący kontroluje dane wejściowe, sytuacja może stać się znacznie bardziej interesująca.
Nie chodzi już o zwykły crash.
Atakujący może próbować zmienić:
- wskaźniki,
- strukturę obiektów,
- dane kontrolujące przepływ programu,
- adresy funkcji,
- metadane alokatora,
- inne dane znajdujące się w pamięci procesu.
To jeden z powodów, dla których błędy pamięciowe od lat są tak istotne w bezpieczeństwie przeglądarek, multimediów, Bluetooth, sieci i komponentów systemowych.
Use-after-free jest jeszcze bardziej zdradliwe
Druga klasyczna klasa błędów:
allocate()
↓
object exists
↓
free()
↓
memory returned
↓
pointer nadal istnieje
↓
use(pointer)
Program korzysta z obiektu, który został już zwolniony.
Przykład koncepcyjny:
struct object *obj = allocate_object();
free(obj);
/* ... */
use_object(obj);
Po free() pamięć może zostać ponownie wykorzystana.
Atakujący próbuje doprowadzić do sytuacji:
OLD OBJECT
↓
free()
↓
ATTACKER CONTROLLED OBJECT
↓
stary pointer
↓
nowa zawartość
Wtedy błąd pamięci może stać się prymitywem exploita.
To właśnie ten typ problemu MTE próbuje wykrywać na poziomie sprzętowym.
MTE dodaje pamięci „tag”
Najważniejsza idea MTE jest zaskakująco prosta.
Do pamięci przypisywany jest dodatkowy tag.
Wskaźnik również posiada informację pozwalającą dopasować go do taga pamięci.
W uproszczeniu:
POINTER
┌─────────────────────────────┐
│ address TAG │
└─────────────────────────────┘
│
▼
MEMORY
┌─────────────────────────────┐
│ data TAG │
└─────────────────────────────┘
Procesor sprawdza:
tag pointer == tag memory ?
Jeżeli tak:
ACCESS = OK
Jeżeli nie:
ACCESS = FAULT
A więc zamiast pozwolić błędnemu wskaźnikowi spokojnie kontynuować działanie:
bug
↓
memory corruption
↓
exploit
otrzymujemy:
bug
↓
tag mismatch
↓
fault
↓
process termination / diagnostic
To fundamentalna zmiana.
MTE nie naprawia błędu
To trzeba podkreślić.
MTE nie jest magicznym mechanizmem, który sprawia, że kod C/C++ staje się bezpieczny.
Program nadal może zawierać:
buffer overflow
use-after-free
out-of-bounds access
Różnica polega na tym, że sprzęt może wykryć określone przypadki naruszenia granicy pamięci.
Czyli:
MTE ≠ bug prevention
bardziej:
MTE = bug detection + exploitation mitigation
To bardzo istotne rozróżnienie.
Dlaczego MTE jest interesujące właśnie dla Androida?
Android ma ogromną bazę kodu natywnego.
Duża część systemu i krytycznych komponentów wykorzystuje C/C++.
Dotyczy to między innymi komponentów związanych z:
- multimediami,
- Bluetooth,
- grafiką,
- sterownikami,
- siecią,
- system services,
- parserami danych,
- formatami plików.
To dokładnie te miejsca, gdzie niekontrolowane dane wejściowe spotykają się z kodem natywnym.
Schemat ryzyka wygląda często tak:
UNTRUSTED INPUT
│
▼
parser / decoder
│
▼
native C/C++
│
▼
memory bug
│
▼
memory corruption
│
▼
exploit
MTE próbuje przeciąć ten łańcuch na poziomie dostępu do pamięci.
Android już wykorzystuje MTE
AOSP wspiera MTE od Androida 12, a urządzenia wykorzystujące Armv9 mogą korzystać ze sprzętowej implementacji tagowanej pamięci.
Google opisuje trzy tryby działania:
SYNC
ASYNC
ASYMM
Ich przeznaczenie jest różne.
Nie chodzi więc o prosty przełącznik:
MTE = ON
Architektura jest bardziej interesująca.
MTE SYNC
Tryb synchroniczny stawia na dokładność.
Jeżeli nastąpi niezgodność taga, procesor może natychmiast przerwać wykonanie.
Schemat:
memory access
│
▼
tag comparison
│
mismatch
│
▼
immediate fault
│
▼
SIGSEGV
Android może wtedy dostarczyć bardzo dokładnych informacji diagnostycznych.
To ogromna zaleta podczas developmentu.
Developer nie musi tylko wiedzieć:
„Aplikacja się wywaliła.”
Może dostać informację, że wystąpił konkretny błąd związany z oznaczaniem pamięci.
MTE ASYNC
Tryb asynchroniczny preferuje wydajność.
Procesor może wykryć niezgodność, ale zgłoszenie błędu nie musi nastąpić dokładnie w momencie błędnego dostępu.
W praktyce:
bad access
↓
tag mismatch
↓
hardware records fault
↓
later kernel entry
↓
process terminated
To zmniejsza narzut względem najbardziej restrykcyjnego trybu.
Cena jest oczywista:
diagnostyka jest mniej precyzyjna.
Dlatego ASYNC jest interesujący w środowiskach produkcyjnych, gdzie narzut wydajności ma znaczenie.
SYNC kontra ASYNC
| Cecha | SYNC | ASYNC |
|---|---|---|
| Reakcja | natychmiastowa | opóźniona |
| Diagnostyka | bardzo dokładna | mniej dokładna |
| Narzut | większy | mniejszy |
| Development | bardzo dobry | dobry |
| Produkcja | wybrane przypadki | bardziej praktyczny |
To pokazuje, że bezpieczeństwo sprzętowe zawsze jest kompromisem.
Nie wystarczy powiedzieć:
„włączmy najbardziej agresywną ochronę”.
Trzeba jeszcze odpowiedzieć:
jaki jest koszt?
MTE kontra klasyczny ASLR
MTE nie zastępuje ASLR.
To dwa różne mechanizmy.
ASLR próbuje utrudnić przewidywanie adresów.
MTE próbuje wykrywać nieprawidłowe użycie pamięci.
Możemy to przedstawić:
ASLR
│
├── utrudnia przewidywanie adresów
│
▼
eksploitowanie pamięci
│
▼
MTE
│
├── sprawdza zgodność tagów
│
▼
memory corruption
W praktyce oba mechanizmy mogą działać jednocześnie.
To jest właśnie siła defense in depth.
MTE kontra CFI
Control Flow Integrity działa jeszcze gdzie indziej.
CFI próbuje ograniczyć możliwość przekierowania wykonywania programu poza dozwolone ścieżki.
MTE skupia się na pamięci.
Czyli:
MEMORY SAFETY
┌───────────────┐
│ MTE │
│ memory access │
└───────┬───────┘
│
┌───────▼───────┐
│ CFI │
│ control flow │
└───────┬───────┘
│
┌───────▼───────┐
│ ASLR │
│ address rand. │
└───────────────┘
Żaden z tych mechanizmów nie rozwiązuje całego problemu.
Razem robią coś znacznie ciekawszego.
Co MTE robi z use-after-free?
To jeden z najciekawszych przypadków.
Wyobraźmy sobie:
Object A
TAG = 5
Proces zwalnia pamięć:
free(Object A)
Pamięć może zostać wykorzystana ponownie.
Nowa alokacja może otrzymać inny tag:
Object B
TAG = 9
Stary wskaźnik nadal zawiera:
TAG = 5
A więc:
OLD POINTER
TAG 5
│
▼
MEMORY
TAG 9
5 != 9
↓
TAG FAULT
To bardzo eleganckie rozwiązanie problemu.
Nie trzeba wiedzieć, że programista popełnił błąd.
Sprzęt może wykryć, że:
wskaźnik nie pasuje do pamięci.
To komplikuje życie exploit developerom
Klasyczny exploit memory corruption często wymaga uzyskania określonego prymitywu.
Na przykład:
arbitrary read
arbitrary write
controlled pointer
code execution
MTE może utrudnić uzyskanie tych prymitywów.
Atakujący musi teraz nie tylko kontrolować dane.
Musi również radzić sobie z mechanizmem tagowania.
To zwiększa koszt tworzenia exploita.
I to jest prawdopodobnie najważniejsza wartość MTE.
Nie chodzi o to, że:
„MTE eliminuje exploity”.
Nie eliminuje.
Chodzi o:
podniesienie kosztu i zmniejszenie niezawodności eksploatacji błędów pamięciowych.
MTE nie jest firewallem pamięci
To również częsty błąd w rozumieniu tej technologii.
MTE nie mówi:
proces A nie może czytać pamięci procesu B
Od tego są inne mechanizmy:
- MMU,
- page tables,
- proces isolation,
- SELinux,
- sandboxing,
- kernel permissions,
- hypervisor.
MTE działa wewnątrz przestrzeni pamięci dostępnej procesowi.
To bardzo ważne.
Schemat:
PROCESS A
│
├── virtual address space
│
├── page tables
│
└── MTE
│
└── tag validation
MTE nie zastępuje izolacji procesów.
Uzupełnia ją.
Android Security działa warstwowo
I właśnie tutaj MTE staje się naprawdę interesujące.
Android nie polega na jednym mechanizmie.
Możemy uprościć architekturę:
HARDWARE
│
┌───────▼───────┐
│ MTE │
└───────┬───────┘
│
┌───────▼───────┐
│ MMU │
└───────┬───────┘
│
┌───────▼───────┐
│ Kernel │
└───────┬───────┘
│
┌───────▼───────┐
│ SELinux │
└───────┬───────┘
│
┌───────▼───────┐
│ App sandbox │
└───────┬───────┘
│
┌───────▼───────┐
│ Application │
└───────────────┘
A nad tym pojawiają się jeszcze:
Verified Boot
dm-verity
hardware-backed keys
TEE
pKVM
CFI
ASLR
seccomp
Każda warstwa ma inny cel.
pKVM pokazuje, dokąd zmierza Android
Współczesny Android rozwija także Protected KVM — pKVM.
To jeszcze inna filozofia izolacji.
pKVM wykorzystuje warstwę hypervisora do izolowania chronionych maszyn wirtualnych od hostującego Androida.
AOSP opisuje pKVM jako mechanizm mający zapewnić poufność i integralność pamięci chronionych VM nawet w sytuacji kompromitacji hosta.
Schemat:
ANDROID HOST
│
┌─────▼─────┐
│ Linux EL1 │
└─────┬─────┘
│
──────┼──────
│
┌─────▼─────┐
│ pKVM │
│ EL2 │
└─────┬─────┘
│
┌───────┴────────┐
│ │
pVM #1 pVM #2
To już nie jest klasyczne:
aplikacja jest sandboxowana.
To znacznie głębsza izolacja.
MTE i pKVM rozwiązują różne problemy
Nie należy ich ze sobą mylić.
MTE:
memory safety
pKVM:
isolation
SELinux:
mandatory access control
Verified Boot:
boot integrity
Keystore / TEE:
key isolation
Razem tworzą coś znacznie bardziej interesującego niż pojedynczy „antywirus”.
MTE jest szczególnie ważne dla komponentów uprzywilejowanych
AOSP zaleca wykorzystanie MTE dla określonych natywnych komponentów, szczególnie tych, które są uprzywilejowane lub obsługują dane wejściowe z niezaufanych źródeł. W dokumentacji wymieniane są m.in. komponenty związane z NFC, Bluetooth i Secure Element.
To logiczne.
Największa wartość MTE pojawia się tam, gdzie jednocześnie występują:
untrusted input
+
native code
+
high privileges
Przykładowo:
Bluetooth packet
↓
Bluetooth stack
↓
C/C++
↓
memory bug
↓
privileged process
To jest znacznie ciekawszy cel dla MTE niż zwykła aplikacja kalkulatora.
Co oznacza MTE dla zero-dayów?
Tutaj dochodzimy do bardzo ważnego punktu.
Jeżeli podatność istnieje:
CVE
↓
memory corruption
to sama obecność MTE może zmienić sytuację.
Niektóre exploity mogą:
crash
zamiast:
code execution
Inne mogą wymagać dodatkowych etapów.
Jeszcze inne mogą próbować wykorzystać przypadki, których MTE nie wykryje.
Dlatego nie można mówić:
„MTE blokuje CVE”.
Prawidłowo:
MTE może ograniczyć możliwość wykorzystania określonych klas błędów pamięciowych i zwiększyć koszt budowy niezawodnego exploita.
To dużo bardziej precyzyjne.
MTE nie ochroni przed każdym błędem
MTE ma ograniczenia.
Nie rozwiązuje automatycznie:
logic bugs
authentication bypass
authorization flaws
race conditions
cryptographic mistakes
information disclosure
design flaws
server-side vulnerabilities
Nie każdy błąd pamięciowy musi również zostać wykryty w sposób, który uniemożliwi wykorzystanie.
Dlatego MTE jest mitigacją, a nie kompletnym systemem bezpieczeństwa.
Co z kompatybilnością aplikacji?
To jeden z powodów, dla których Android nie włącza MTE bezwarunkowo dla wszystkiego.
Aplikacja może zawierać kod natywny, który:
- zakłada określone zachowanie wskaźników,
- korzysta z nietypowych technik alokacji,
- posiada błędy ujawniające się dopiero przy tagowaniu,
- ma niekompatybilność z MTE.
Dlatego aplikacje wykorzystujące MTE powinny być odpowiednio testowane.
AOSP opisuje konfigurację MTE per aplikacja oraz możliwość określenia trybu przez android:memtagMode.
To daje deweloperom znacznie większą kontrolę.
Jak rozpoznać błąd MTE?
W raportach Androida mogą pojawić się między innymi:
SEGV_MTESERR
SEGV_MTEAERR
Są to sygnały związane z błędami tagowania pamięci.
To bardzo przydatne podczas analizy crashy.
Przykładowy schemat:
Application crash
│
▼
SIGSEGV
│
├── normal memory fault
│
└── MTE fault
│
├── SEGV_MTESERR
└── SEGV_MTEAERR
Dla zespołu bezpieczeństwa może to być bardzo wartościowy sygnał.
MTE zmienia również sposób testowania aplikacji
W klasycznym modelu developer może długo nie wiedzieć, że kod ma błąd.
Program działa.
Testy przechodzą.
Użytkownik wykonuje konkretną sekwencję.
Dopiero wtedy:
memory corruption
MTE może ujawnić problem wcześniej.
Dlatego MTE jest równie interesujące jako:
narzędzie security testing
jak jako:
mechanizm ochrony produkcyjnego urządzenia.
To ogromna różnica względem klasycznego postrzegania sprzętowych mechanizmów bezpieczeństwa.
Największy problem Androida może więc przesunąć się niżej
Przez lata można było myśleć o bezpieczeństwie Androida głównie tak:
malware
↓
permissions
↓
sandbox
↓
user data
Nowoczesny model jest znacznie głębszy:
App
↓
Sandbox
↓
SELinux
↓
Native code
↓
Memory safety
↓
MTE
↓
Kernel
↓
pKVM
↓
Hardware
To właśnie pokazuje kierunek rozwoju Androida.
Im bardziej zaawansowane są ataki, tym niżej schodzi obrona.
Czy MTE zakończy erę exploitów Androida?
Nie.
Ale może zakończyć erę bardzo tanich exploitów opartych na części klasycznych błędów pamięci.
To ogromna różnica.
Atakujący nadal może szukać:
logic bug
↓
information leak
↓
kernel vulnerability
↓
driver vulnerability
↓
privilege escalation
Może też próbować znaleźć błędy w samym mechanizmie implementacji.
Ale jeżeli jeden element łańcucha exploita zaczyna wymagać obsługi tagów pamięci, jego stworzenie staje się bardziej skomplikowane.
A w świecie exploit development złożoność ma znaczenie.
Android zmierza w stronę sprzętowo wymuszanej memory safety
To jest moim zdaniem najważniejsza rzecz.
Nie chodzi tylko o MTE.
Trend jest szerszy:
Software mitigations
+
Compiler protections
+
Runtime checks
+
Hardware enforcement
+
Isolation
Zamiast liczyć wyłącznie na to, że programista nie popełni błędu, system próbuje ograniczyć konsekwencje błędu.
To bardzo podobna filozofia do tej, która stoi za:
- HVCI w Windows,
- CFI,
- NX,
- SMEP,
- SMAP,
- PAC,
- pKVM,
- Secure Boot.
System operacyjny przestaje być tylko zbiorem usług.
Staje się aktywnym uczestnikiem kontroli bezpieczeństwa.
Co powinien wiedzieć użytkownik Androida?
Użytkownik nie musi ręcznie konfigurować MTE.
Nie powinien też instalować przypadkowych narzędzi obiecujących „włączenie wszystkich zabezpieczeń”.
Znacznie ważniejsze jest korzystanie z urządzenia, które:
✓ ma aktualne poprawki bezpieczeństwa
✓ korzysta ze współczesnego SoC
✓ ma Verified Boot
✓ otrzymuje regularne aktualizacje
✓ korzysta z nowoczesnych mechanizmów sprzętowych
W praktyce MTE jest przede wszystkim technologią platformową.
To producent urządzenia i twórcy systemu decydują, gdzie i w jakim trybie jest wykorzystywane.
A co powinien zrobić developer?
Tutaj sytuacja jest zupełnie inna.
Jeżeli aplikacja posiada dużo kodu natywnego:
C
C++
JNI
NDK
native libraries
MTE powinno być traktowane poważnie.
Szczególnie jeśli aplikacja:
przetwarza pliki
odbiera dane sieciowe
obsługuje Bluetooth
dekoduje multimedia
korzysta z parserów
ma wysokie uprawnienia
Wtedy memory corruption nie jest tylko problemem jakości.
Jest problemem bezpieczeństwa.
Najważniejsza zmiana
MTE zmienia jedno fundamentalne założenie.
W przeszłości bezpieczeństwo pamięci często sprowadzało się do:
„Nie popełniaj błędów.”
Teraz możemy powiedzieć:
„Nie popełniaj błędów, ale jeżeli popełnisz — sprzęt może pomóc ograniczyć skutki.”
To jest znacznie bardziej realistyczny model.
Programiści nadal będą popełniać błędy.
Kernel nadal będzie miał błędy.
Biblioteki nadal będą miały błędy.
Parser nadal może dostać dane, których nikt nie przewidział.
Różnica polega na tym, że Android coraz częściej próbuje umieścić sprzętową granicę bezpieczeństwa pod kodem podatnym na błędy.
Podsumowanie
Memory Tagging Extension nie jest kolejnym marketingowym przełącznikiem w specyfikacji smartfona.
To fundamentalna zmiana w podejściu do memory safety.
Schemat jest prosty:
MEMORY BUG
│
▼
invalid access
│
▼
┌──────────────┐
│ MTE │
│ tag checking │
└──────┬───────┘
│
┌─────┴─────┐
│ │
MATCH MISMATCH
│ │
▼ ▼
ACCESS FAULT
MTE nie eliminuje podatności.
Nie zastępuje SELinux.
Nie zastępuje sandboxingu.
Nie zastępuje Verified Boot.
Nie zastępuje pKVM.
Nie zastępuje aktualizacji bezpieczeństwa.
Ale może sprawić, że błąd pamięciowy pozostanie błędem pamięciowym zamiast stać się działającym exploitem.
I właśnie dlatego technologia ta jest tak istotna dla przyszłości Androida.
Najciekawsze w MTE nie jest nawet to, że potrafi wykrywać use-after-free.
Najciekawsze jest coś innego:
Android zaczyna przenosić część odpowiedzialności za bezpieczeństwo z programisty na sprzęt.
A kiedy sprzęt zaczyna kontrolować poprawność dostępu do pamięci, koszt budowania kolejnej generacji exploitów rośnie razem z poziomem ochrony platformy.
Autor
Marek „Netbe” Lampart — Inżynier Informatyki
Praktyk systemów Windows/Linux, Androida i cyberbezpieczeństwa, zajmujący się bezpieczeństwem systemów operacyjnych oraz analizą mechanizmów ochronnych.
Źródła techniczne
- Android Open Source Project — dokumentacja Arm Memory Tagging Extension (MTE).
- Android Open Source Project — MTE configuration.
- Android Open Source Project — MTE reports i kody błędów
SEGV_MTESERR/SEGV_MTEAERR. - Android Open Source Project — architektura bezpieczeństwa pKVM / Android Virtualization Framework.







