Mit Arm v9 wird die Arm Memory Tagging Extension (MTE) eingeführt, eine Hardware-Implementierung von getaggtem Arbeitsspeicher.
Auf hoher Ebene taggt MTE jede Speicherzuweisung/-freigabe mit zusätzlichen Metadaten. Es wird ein Tag einem Speicherort zugewiesen, der dann mit Zeigern verknüpft werden kann, die auf diesen Speicherort verweisen. Zur Laufzeit prüft die CPU bei jedem Lade- und Speichervorgang, ob der Zeiger und die Metadatentags übereinstimmen.
In Android 12 kann der Kernel- und Userspace-Heap-Speicher-Allocator jeder Zuweisung Metadaten hinzufügen. So lassen sich Use-After-Free- und Pufferüberlauffehler erkennen, die die häufigste Quelle für Arbeitssicherheitsprobleme in unseren Codebases sind.
MTE-Betriebsmodi
MTE hat drei Betriebsmodi:
- Synchroner Modus (SYNC)
- Asynchroner Modus (ASYNC)
- Asymmetrischer Modus (ASYMM)
Synchroner Modus (SYNC)
Dieser Modus ist für die Korrektheit der Fehlererkennung optimiert und kann als präzises Tool zur Fehlererkennung verwendet werden, wenn ein höherer Leistungsaufwand akzeptabel ist. Wenn MTE SYNC aktiviert ist, dient es als Sicherheitsmaßnahme.
Bei einer Tag-Nichtübereinstimmung bricht der Prozessor die Ausführung sofort ab und beendet den Prozess mit SIGSEGV
(Code SEGV_MTESERR
) und vollständigen Informationen zum Speicherzugriff und zur fehlerhaften Adresse.
Wir empfehlen, diesen Modus während des Testens als Alternative zu HWASan/KASAN oder in der Produktion zu verwenden, wenn der Zielprozess eine anfällige Angriffsfläche darstellt. Wenn im ASYNC-Modus ein Fehler erkannt wurde, kann außerdem ein genauer Fehlerbericht erstellt werden, indem die Ausführung mithilfe der Runtime-APIs in den SYNC-Modus umgeschaltet wird.
Im SYNC-Modus zeichnet der Android-Allocator Stacktraces für alle Zuweisungen und Freigaben auf und verwendet sie, um bessere Fehlerberichte zu erstellen, die eine Erklärung eines Speicherfehlers wie „Use-After-Free“ oder „Pufferüberlauf“ sowie die Stacktraces der relevanten Speicherereignisse enthalten. Solche Berichte enthalten mehr Kontextinformationen und erleichtern das Nachvollziehen und Beheben von Fehlern.
Asynchroner Modus (ASYNC)
Dieser Modus ist auf Leistung und nicht auf die Genauigkeit von Fehlerberichten optimiert und kann als ressourcenschonende Methode zum Erkennen von Speichersicherheitsfehlern verwendet werden.
Bei einer Tag-Nichtübereinstimmung wird die Ausführung fortgesetzt, bis der nächste Kerneleintrag (z. B. ein Systemaufruf oder ein Timer-Interrupt) erreicht wird. Dort wird der Prozess mit SIGSEGV
(Code SEGV_MTEAERR
) beendet, ohne die fehlerhafte Adresse oder den Speicherzugriff aufzuzeichnen.
Wir empfehlen, diesen Modus in der Produktion für gut getestete Codebases zu verwenden, bei denen die Dichte von Speichersicherheitsfehlern bekanntermaßen gering ist. Dies wird durch die Verwendung des SYNC-Modus während des Tests erreicht.
Asymmetrischer Modus (ASYMM)
Eine zusätzliche Funktion in Arm v8.7-A ist der asymmetrische MTE-Modus. Er bietet synchrone Prüfungen für das Lesen von Daten aus dem Speicher und asynchrone Prüfungen für das Schreiben von Daten in den Speicher. Die Leistung ist dabei ähnlich wie im ASYNC-Modus. In den meisten Fällen ist dieser Modus eine Verbesserung gegenüber dem ASYNC-Modus. Wir empfehlen, ihn anstelle von ASYNC zu verwenden, sofern er verfügbar ist.
Aus diesem Grund wird der asymmetrische Modus in keiner der unten beschriebenen APIs erwähnt. Stattdessen kann das Betriebssystem so konfiguriert werden, dass immer der asymmetrische Modus verwendet wird, wenn der asynchrone Modus angefordert wird. Weitere Informationen finden Sie im Abschnitt „CPU-spezifisches bevorzugtes MTE-Level konfigurieren“.
MTE im Nutzerbereich
In den folgenden Abschnitten wird beschrieben, wie MTE für Systemprozesse und Apps aktiviert werden kann. MTE ist standardmäßig deaktiviert, sofern nicht eine der folgenden Optionen für einen bestimmten Prozess festgelegt ist (siehe unten, für welche Komponenten MTE aktiviert ist).
MTE über das Build-System aktivieren
Als prozessweite Eigenschaft wird MTE durch die Build-Time-Einstellung der Haupt-Executable gesteuert. Mit den folgenden Optionen kann diese Einstellung für einzelne ausführbare Dateien oder für ganze Unterverzeichnisse im Quellbaum geändert werden. Die Einstellung wird bei Bibliotheken oder Zielen ignoriert, die weder ausführbar noch ein Test sind.
1. So aktivieren Sie MTE in Android.bp
(Beispiel) für ein bestimmtes Projekt:
MTE-Modus | Einstellung |
---|---|
Asynchrone MTE | sanitize: { memtag_heap: true, } |
Synchroner MTE | sanitize: { memtag_heap: true, diag: { memtag_heap: true, }, } |
oder in Android.mk:
MTE-Modus | Einstellung |
---|---|
Asynchronous MTE |
LOCAL_SANITIZE := memtag_heap |
Synchronous MTE |
LOCAL_SANITIZE := memtag_heap LOCAL_SANITIZE_DIAG := memtag_heap |
2. MTE für ein Unterverzeichnis im Quellbaum mit einer Produktvariablen aktivieren:
MTE-Modus | Einschlussliste | Ausschlussliste |
---|---|---|
async | PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
Sync | PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS
MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
oder
MTE-Modus | Einstellung |
---|---|
Asynchrone MTE | MEMTAG_HEAP_ASYNC_INCLUDE_PATHS |
Synchroner MTE | MEMTAG_HEAP_SYNC_INCLUDE_PATHS |
oder indem Sie den Ausschluss-Pfad einer ausführbaren Datei angeben:
MTE-Modus | Einstellung |
---|---|
Asynchrone MTE | PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS
MEMTAG_HEAP_EXCLUDE_PATHS |
Synchroner MTE |
Beispiel (ähnliche Verwendung wie PRODUCT_CFI_INCLUDE_PATHS
):
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor) PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \ vendor/$(vendor)/projectB
MTE über Systemeigenschaften aktivieren
Die oben genannten Buildeinstellungen können zur Laufzeit überschrieben werden, indem Sie die folgende Systemeigenschaft festlegen:
arm64.memtag.process.<basename> = (off|sync|async)
Dabei steht basename
für den Basisnamen der ausführbaren Datei.
Wenn Sie beispielsweise /system/bin/ping
oder /data/local/tmp/ping
für die Verwendung von asynchronem MTE festlegen möchten, verwenden Sie adb shell setprop arm64.memtag.process.ping async
.
MTE mit einer Umgebungsvariablen aktivieren
Eine weitere Möglichkeit, die Buildeinstellung für native Prozesse (keine Apps) zu überschreiben, besteht darin, die Umgebungsvariable MEMTAG_OPTIONS=(off|sync|async)
zu definieren. Wenn sowohl die Umgebungsvariable als auch die Systemeigenschaft definiert sind, hat die Variable Vorrang.
MTE für Apps aktivieren
Wenn nicht angegeben, ist MTE standardmäßig deaktiviert. Apps, die MTE verwenden möchten, können dies tun, indem sie android:memtagMode
unter dem Tag <application>
oder <process>
in der AndroidManifest.xml
festlegen.
android:memtagMode=(off|default|sync|async)
Wenn das Attribut für das <application>
-Tag festgelegt ist, wirkt es sich auf alle von der App verwendeten Prozesse aus. Es kann für einzelne Prozesse überschrieben werden, indem das <process>
-Tag festgelegt wird.
Für Tests können Kompatibilitätsänderungen verwendet werden, um den Standardwert des Attributs memtagMode
für eine App festzulegen, in deren Manifest kein Wert angegeben ist (oder default
angegeben ist).
Sie finden diese unter System > Advanced > Developer options
> App Compatibility Changes
im globalen Einstellungsmenü. Durch Festlegen von NATIVE_MEMTAG_ASYNC
oder NATIVE_MEMTAG_SYNC
wird MTE für eine bestimmte App aktiviert.
Alternativ kann dies mit dem Befehl am
festgelegt werden:
$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name
MTE-System-Image erstellen
Wir empfehlen dringend, MTE während der Entwicklung und Inbetriebnahme für alle nativen Binärdateien zu aktivieren. So können Speicherfehler frühzeitig erkannt werden. Außerdem wird eine realistische Nutzerabdeckung erreicht, wenn die Funktion in Test-Builds aktiviert ist.
Wir empfehlen dringend, MTE im synchronen Modus für alle nativen Binärdateien während der Entwicklung zu aktivieren.
SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m
Wie jede Variable im Build-System kann SANITIZE_TARGET
als Umgebungsvariable oder als make
-Einstellung verwendet werden, z. B. in einer product.mk
-Datei.
Dadurch wird MTE für alle nativen Prozesse aktiviert, nicht jedoch für Apps (die von zygote64
abgeleitet werden), für die MTE gemäß der Anleitung oben aktiviert werden kann.
CPU-spezifische bevorzugte MTE-Stufe konfigurieren
Bei einigen CPUs kann die Leistung von MTE im ASYMM- oder sogar im SYNC-Modus ähnlich wie im ASYNC-Modus sein. Daher lohnt es sich, strengere Prüfungen für diese CPUs zu aktivieren, wenn ein weniger strenger Prüfmodus angefordert wird, um die Vorteile der Fehlererkennung durch die strengeren Prüfungen ohne die Nachteile in Bezug auf die Leistung zu nutzen.
Prozesse, die für die Ausführung im ASYNC-Modus konfiguriert sind, werden standardmäßig im ASYNC-Modus auf allen CPUs ausgeführt. Wenn Sie den Kernel so konfigurieren möchten, dass diese Prozesse im SYNC-Modus auf bestimmten CPUs ausgeführt werden, muss der Wert „sync“ beim Booten in den sysfs
-Eintrag /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred
geschrieben werden. Dies kann mit einem Initialisierungsskript erfolgen. Wenn Sie beispielsweise die CPUs 0–1 so konfigurieren möchten, dass Prozesse im ASYNC-Modus im SYNC-Modus ausgeführt werden, und die CPUs 2–3 so, dass sie im ASYMM-Modus ausgeführt werden, kann der Init-Klausel eines Anbieter-Init-Skripts Folgendes hinzugefügt werden:
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 von ASYNC-Modusprozessen, die im SYNC-Modus ausgeführt werden, enthalten einen genauen Stacktrace des Speicherfehlers. Sie enthalten jedoch keinen Stacktrace für die Zuordnung oder Aufhebung der Zuordnung. Diese Stacktraces sind nur verfügbar, wenn der Prozess für die Ausführung im SYNC-Modus konfiguriert ist.
int mallopt(M_THREAD_DISABLE_MEM_INIT, level)
Dabei ist level
0 oder 1.
Deaktiviert die Speicherinitialisierung in malloc und vermeidet das Ändern von Speichertags, sofern dies nicht für die Korrektheit erforderlich ist.
int mallopt(M_MEMTAG_TUNING, level)
Dabei gilt:level
M_MEMTAG_TUNING_BUFFER_OVERFLOW
M_MEMTAG_TUNING_UAF
Wählt die Strategie für die Tag-Zuweisung aus.
- Die Standardeinstellung ist
M_MEMTAG_TUNING_BUFFER_OVERFLOW
. M_MEMTAG_TUNING_BUFFER_OVERFLOW
: Ermöglicht die deterministische Erkennung von linearen Pufferüberlauf- und ‑unterlauf-Bugs, indem benachbarten Zuweisungen unterschiedliche Tag-Werte zugewiesen werden. In diesem Modus ist die Wahrscheinlichkeit, Use-after-Free-Fehler zu erkennen, etwas geringer, da für jeden Speicherort nur die Hälfte der möglichen Tag-Werte verfügbar ist. Beachten Sie, dass MTE keinen Überlauf innerhalb derselben Tag-Granule (16‑Byte-ausgerichteter Chunk) erkennen kann und selbst in diesem Modus kleine Überläufe möglicherweise nicht erkannt werden. Ein solcher Überlauf kann nicht die Ursache für eine Beschädigung des Arbeitsspeichers sein, da der Arbeitsspeicher innerhalb eines Granulats nie für mehrere Zuweisungen verwendet wird.M_MEMTAG_TUNING_UAF
– ermöglicht unabhängig randomisierte Tags für eine einheitliche Wahrscheinlichkeit von etwa 93 %, sowohl räumliche (Pufferüberlauf) als auch zeitliche (Use-After-Free) Fehler zu erkennen.
Zusätzlich zu den oben beschriebenen APIs sollten erfahrene Nutzer Folgendes beachten:
- Durch Festlegen des Hardware-Registers
PSTATE.TCO
kann die Tag-Prüfung vorübergehend unterdrückt werden (Beispiel). Das kann beispielsweise beim Kopieren eines Speicherbereichs mit unbekannten Tag-Inhalten oder beim Beheben eines Leistungsengpasses in einer Hot Loop der Fall sein. - Wenn Sie
M_HEAP_TAGGING_LEVEL_SYNC
verwenden, stellt der System-Crash-Handler zusätzliche Informationen wie Zuweisungs- und Freigabe-Stacktraces bereit. Für diese Funktion ist Zugriff auf die Tag-Bits erforderlich. Sie wird aktiviert, indem Sie das FlagSA_EXPOSE_TAGBITS
beim Festlegen des Signalhandlers übergeben. Für jedes Programm, das einen eigenen Signalhandler festlegt und unbekannte Abstürze an den Systemhandler delegiert, wird empfohlen, dies ebenfalls zu tun.
MTE im Kernel
Um MTE-beschleunigtes KASAN für den Kernel zu aktivieren, konfigurieren Sie den Kernel mit CONFIG_KASAN=y
, CONFIG_KASAN_HW_TAGS=y
. Diese Konfigurationen sind ab Android
12-5.10
standardmäßig für GKI-Kernel aktiviert.
Dies kann beim Booten mit den folgenden Befehlszeilenargumenten gesteuert werden:
kasan=[on|off]
: KASAN aktivieren oder deaktivieren (Standard:on
)kasan.mode=[sync|async]
– zwischen synchronem und asynchronem Modus wählen (Standard:sync
)kasan.stacktrace=[on|off]
: Gibt an, ob Stacktraces erfasst werden sollen (Standard:on
).- Für die Erfassung von Stacktraces ist auch
stack_depot_disable=off
erforderlich.
- Für die Erfassung von Stacktraces ist auch
kasan.fault=[report|panic]
– gibt an, ob nur der Bericht ausgegeben oder auch der Kernel in den Panikmodus versetzt werden soll (Standard:report
). Unabhängig von dieser Option wird die Tag-Prüfung nach dem ersten gemeldeten Fehler deaktiviert.
Empfohlene Verwendung
Wir empfehlen dringend, den SYNC-Modus während der Inbetriebnahme, Entwicklung und Tests zu verwenden. Diese Option sollte global für alle Prozesse mit der Umgebungsvariable oder mit dem Build-System aktiviert werden. In diesem Modus werden Fehler früh im Entwicklungsprozess erkannt, die Codebasis wird schneller stabilisiert und die Kosten für die Erkennung von Fehlern später in der Produktion werden vermieden.
Wir empfehlen dringend, in der Produktion den ASYNC-Modus zu verwenden. Dies ist ein Tool mit geringem Overhead, mit dem das Vorhandensein von Speicherfehlern in einem Prozess erkannt werden kann. Außerdem bietet es eine zusätzliche Defense-in-Depth-Schicht. Wird ein Fehler erkannt, kann der Entwickler die Runtime-APIs nutzen, um in den SYNC-Modus zu wechseln und einen genauen Stacktrace von einer Stichprobe von Nutzern zu erhalten.
Wir empfehlen dringend, die CPU-spezifische bevorzugte MTE-Ebene für den SoC zu konfigurieren. Der asymmetrische Modus hat in der Regel dieselben Leistungsmerkmale wie ASYNC und ist fast immer vorzuziehen. Kleine In-Order-Cores weisen in allen drei Modi oft eine ähnliche Leistung auf und können so konfiguriert werden, dass SYNC bevorzugt wird.
Entwickler sollten das Vorhandensein von Abstürzen prüfen, indem sie /data/tombstones
, logcat
oder die DropboxManager
-Pipeline des Anbieters auf Endnutzerfehler überwachen. Weitere Informationen zum Debuggen von nativem Android-Code finden Sie hier.
MTE-fähige Plattformkomponenten
In Android 12 verwenden mehrere sicherheitskritische Systemkomponenten MTE ASYNC, um Abstürze von Endnutzern zu erkennen und als zusätzliche Ebene der Defense-in-Depth zu fungieren. Diese Komponenten sind:
- Netzwerk-Daemons und ‑Dienstprogramme (mit Ausnahme von
netd
) - Bluetooth, SecureElement, NFC-HALs und System-Apps
statsd
-Daemonsystem_server
zygote64
(damit Apps die Verwendung von MTE aktivieren können)
Diese Ziele wurden anhand der folgenden Kriterien ausgewählt:
- Ein privilegierter Prozess (definiert als ein Prozess, der Zugriff auf etwas hat, auf das die SELinux-Domain „unprivileged_app“ keinen Zugriff hat)
- Verarbeitet nicht vertrauenswürdige Eingaben (Regel der zwei)
- Akzeptable Verlangsamung der Leistung (die Verlangsamung führt nicht zu einer für den Nutzer sichtbaren Latenz)
Wir empfehlen Anbietern, MTE in der Produktion für weitere Komponenten zu aktivieren, sofern die oben genannten Kriterien erfüllt sind. Während der Entwicklung empfehlen wir, diese Komponenten im SYNC-Modus zu testen, um leicht zu behebende Fehler zu erkennen und die Auswirkungen des ASYNC-Modus auf die Leistung zu bewerten.
In Zukunft soll die Liste der Systemkomponenten, auf denen MTE aktiviert ist, erweitert werden. Dabei werden die Leistungsmerkmale zukünftiger Hardware-Designs berücksichtigt.