Arm Memory Tagging Extension

Mit Arm v9 wird die Arm Memory Tagging Extension (MTE) eingeführt, eine Hardwareimplementierung von getaggtem Arbeitsspeicher.

Im Wesentlichen taggt MTE jede Arbeitsspeicherzuweisung und ‑freigabe mit zusätzlichen Metadaten. MTE weist einem Arbeitsspeicherort ein Tag zu, das dann mit Zeigern verknüpft werden kann, die auf diesen Arbeitsspeicherort verweisen. Zur Laufzeit prüft die CPU bei jedem Lade- und Speichervorgang, ob der Zeiger und die Metadaten-Tags übereinstimmen.

In Android 12 kann der Kernel- und Userspace-Heap-Arbeitsspeicher-Allocator jede Zuweisung mit Metadaten ergänzen. So lassen sich Use-after-Free- und Pufferüberlauffehler erkennen, die die häufigste Quelle für Arbeitssicherheitsprobleme in unseren Codebasen sind.

MTE-Betriebsmodi

MTE hat drei Betriebsmodi:

  • Synchroner Modus (SYNC)
  • Asynchroner Modus (ASYNC)
  • Asymmetrischer Modus (ASYMM)

Synchroner Modus (SYNC)

Dieser Modus ist auf die Korrektheit der Fehlererkennung und nicht auf die Leistung 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 Arbeitsspeicherzugriff und zur fehlerhaften Adresse.

Wir empfehlen, diesen Modus während des Tests als Alternative zu HWASan/KASAN oder in der Produktion zu verwenden, wenn der Zielprozess eine anfällige Angriffsfläche darstellt. Wenn der ASYNC-Modus auf einen Fehler hinweist, kann außerdem ein genauer Fehlerbericht erstellt werden, indem die Laufzeit-APIs verwendet werden, um die Ausführung in den SYNC-Modus zu wechseln.

Wenn der Android-Allocator im SYNC-Modus ausgeführt wird, zeichnet er Stacktraces für alle Zuweisungen und Freigaben auf und verwendet sie, um bessere Fehlerberichte zu erstellen. Diese enthalten eine Erklärung eines Arbeitsspeicherfehlers, z. B. Use-after-Free oder Pufferüberlauf, sowie die Stacktraces der relevanten Arbeitsspeicherereignisse. Solche Berichte liefern mehr Kontextinformationen und erleichtern das Nachverfolgen und Beheben von Fehlern.

Asynchroner Modus (ASYNC)

Dieser Modus ist auf Leistung und nicht auf die Genauigkeit von Fehlerberichten optimiert. Er kann als ressourcenschonende Erkennung für Arbeitssicherheitsprobleme verwendet werden. Bei einer Tag-Nichtübereinstimmung wird die Ausführung des Prozessors bis zum nächsten Kerneleintrag (z. B. ein Systemaufruf oder eine Timerunterbrechung) fortgesetzt. Dort wird der Prozess mit SIGSEGV (Code SEGV_MTEAERR) beendet, ohne die fehlerhafte Adresse oder den Arbeitsspeicherzugriff aufzuzeichnen.

Wir empfehlen, diesen Modus in der Produktion für gut getestete Codebasen zu verwenden, bei denen die Dichte von Arbeitssicherheitsproblemen 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, der asymmetrische MTE-Modus, bietet synchrone Prüfungen für Arbeitsspeicherlesevorgänge und asynchrone Prüfungen für Arbeitsspeicherschreibvorgänge. Die Leistung ist ä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, wenn 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 es immer den asymmetrischen Modus verwendet, wenn der asynchrone Modus angefordert wird. Weitere Informationen finden Sie unter Bevorzugte MTE-Ebene für die CPU konfigurieren.

MTE im Userspace

In den folgenden Abschnitten wird beschrieben, wie MTE für Systemprozesse und Apps aktiviert werden kann. MTE ist standardmäßig deaktiviert, es sei denn, eine der folgenden Optionen ist für einen bestimmten Prozess festgelegt (siehe unten, für welche Komponenten MTE aktiviert ist unten).

MTE mit dem Build-System aktivieren

Als prozessweite Eigenschaft wird MTE durch die Build-Time-Einstellung von der Hauptausführungsdatei gesteuert. Mit den folgenden Optionen kann diese Einstellung für einzelne Ausführungsdateien oder für ganze Unterverzeichnisse im Quellcodebaum geändert werden. Die Einstellung wird für Bibliotheken oder alle Ziele ignoriert, die weder ausführbar noch ein Test sind.

1. MTE in Android.bp (Beispiel) aktivieren, für ein bestimmtes Projekt:

MTE-Modus Einstellung
Asynchrones MTE
  sanitize: {
  memtag_heap: true,
  }
Synchrones 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 Quellcodebaum mit einer Produktvariablen aktivieren:

MTE-Modus Liste einschließen Liste ausschließen
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
Asynchrones MTE MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
Synchrones MTE MEMTAG_HEAP_SYNC_INCLUDE_PATHS

oder indem Sie den Ausschluss-Pfad einer Ausführungsdatei angeben:

MTE-Modus Einstellung
Asynchrones MTE PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
Synchrones 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 mit Systemeigenschaften aktivieren

Die oben genannten Build-Einstellungen können zur Laufzeit überschrieben werden, indem die folgende Systemeigenschaft festgelegt wird:

arm64.memtag.process.<basename> = (off|sync|async)

Dabei steht basename für den Basisnamen der Ausführungsdatei.

Wenn Sie beispielsweise /system/bin/ping oder /data/local/tmp/ping so einstellen möchten, dass asynchrones MTE verwendet wird, verwenden Sie adb shell setprop arm64.memtag.process.ping async.

MTE mit einer Umgebungsvariablen aktivieren

Eine weitere Möglichkeit, die Build-Einstellung für native Prozesse (keine Apps) zu überschreiben, besteht darin, die Umgebungsvariable zu definieren: MEMTAG_OPTIONS=(off|sync|async) Wenn sowohl die Umgebungsvariable als auch die Systemeigenschaft definiert sind, hat die Variable Vorrang.

MTE für Apps aktivieren

Wenn nicht anders angegeben, ist MTE standardmäßig deaktiviert. Apps, die MTE verwenden möchten, können dies tun, indem sie android:memtagMode unter dem <application> oder <process> Tag in der AndroidManifest.xml festlegen.

android:memtagMode=(off|default|sync|async)

Wenn das Attribut für das Tag <application> festgelegt ist, wirkt es sich auf alle von der App verwendeten Prozesse aus. Es kann für einzelne Prozesse überschrieben werden, indem das Tag <process> festgelegt wird.

Für Tests können Kompatibilitäts änderungen verwendet werden, um den Standardwert des memtagMode Attributs für eine App festzulegen, die keinen Wert im Manifest angibt (oder default angibt).

Diese finden Sie im globalen Einstellungsmenü unter System > Erweitert > Entwickleroptionen > Änderungen bei der App-Kompatibilität. Wenn Sie NATIVE_MEMTAG_ASYNC oder NATIVE_MEMTAG_SYNC festlegen, wird MTE für eine bestimmte App aktiviert. Alternativ kann dies mit dem Befehl am so festgelegt werden:

$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name

MTE-Systemimage erstellen

Wir empfehlen dringend, MTE während der Entwicklung und Inbetriebnahme für alle nativen Binärdateien zu aktivieren. So lassen sich Arbeitssicherheitsprobleme frühzeitig erkennen und eine realistische Nutzerabdeckung erzielen, wenn MTE in Test-Builds aktiviert ist.

Wir empfehlen dringend, MTE während der Entwicklung im synchronen Modus für alle nativen Binärdateien 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, aber nicht für Apps (die von zygote64 abgeleitet werden). Für diese kann MTE gemäß der Anleitung unter MTE für Apps aktivieren aktiviert werden.

Bevorzugte MTE-Ebene für die CPU konfigurieren

Auf einigen CPUs kann die Leistung von MTE im ASYMM- oder sogar im SYNC-Modus ähnlich wie im ASYNC-Modus sein. Daher ist es sinnvoll, strengere Prüfungen auf diesen CPUs zu aktivieren, wenn ein weniger strenger Prüfmodus angefordert wird. So können die Vorteile der Fehlererkennung durch strengere Prüfungen genutzt werden, ohne dass die Leistung beeinträchtigt wird.

Standardmäßig werden Prozesse, die für die Ausführung im ASYNC-Modus konfiguriert sind, auf allen CPUs im ASYNC Modus ausgeführt. Wenn Sie den Kernel so konfigurieren möchten, dass diese Prozesse auf bestimmten CPUs im SYNC-Modus ausgeführt werden, muss der Wert „sync“ zur Boot Zeit in den sysfs Eintrag /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred geschrieben werden. Dies kann mit einem Init-Skript erfolgen. Wenn Sie beispielsweise CPUs 0–1 so konfigurieren möchten, dass Prozesse im async-Modus im SYNC-Modus ausgeführt werden, und CPUs 2–3 so, dass sie im ASYMM-Modus ausgeführt werden, kann der Init-Anweisung 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 Prozessen im ASYNC-Modus, die im SYNC-Modus ausgeführt werden, enthalten einen genauen Stacktrace des Speicherorts des Arbeitsspeicherfehlers. Sie enthalten jedoch keinen Stacktrace für die Zuweisung oder Freigabe. 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 Arbeitsspeicherinitialisierung in malloc und vermeidet das Ändern von Arbeitsspeicher-Tags es sei denn, dies ist für die Korrektheit erforderlich.

int mallopt(M_MEMTAG_TUNING, level)

Dabei ist 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 ‑unterlauffehlern, 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 Arbeitsspeicherort nur die Hälfte der möglichen Tag-Werte verfügbar ist. MTE kann keinen Überlauf innerhalb desselben Tag-Granulats (16-Byte-ausgerichteter Chunk) erkennen und kann selbst in diesem Modus kleine Überläufe übersehen. Ein solcher Überlauf kann nicht die Ursache für eine Arbeitsspeicher beschädigung sein, da der Arbeitsspeicher innerhalb eines Granulats niemals für mehrere Zuweisungen verwendet wird.
  • M_MEMTAG_TUNING_UAF ermöglicht unabhängig randomisierte Tags für eine einheitliche Wahrscheinlichkeit von ~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 desPSTATE.TCO Hardware-Registers kann die Tag-Prüfung vorübergehend unterdrückt werden (Beispiel). Beispielsweise beim Kopieren eines Arbeitsspeicherbereichs mit unbekannten Tag-Inhalten oder beim Beheben eines Leistungsengpasses in einer Hot Loop.
  • Wenn M_HEAP_TAGGING_LEVEL_SYNC verwendet wird, stellt der Systemabsturz-Handler zusätzliche Informationen bereit, z. B. Stacktraces für die Zuweisung und Freigabe. Für diese Funktion ist der Zugriff auf die Tag-Bits erforderlich. Sie wird aktiviert, indem das SA_EXPOSE_TAGBITS Flag beim Festlegen des Signalhandlers übergeben wird. Jedes Programm, das einen eigenen Signal handler festlegt und unbekannte Abstürze an den Systemhandler delegiert, sollte dasselbe tun.

MTE im Kernel

Wenn Sie MTE-beschleunigtes KASAN für den Kernel aktivieren möchten, konfigurieren Sie den Kernel mit CONFIG_KASAN=y, CONFIG_KASAN_HW_TAGS=y. Diese Konfigurationen sind in GKI-Kerneln ab Android 12-5.10 standardmäßig aktiviert.

Sie können dies zur Bootzeit mit den folgenden Befehlszeilenargumenten steuern:

  • 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] : Stacktraces erfassen (Standard: on)
    • Für die Stacktrace-Erfassung ist auch stack_depot_disable=off erforderlich.
  • kasan.fault=[report|panic] – nur den Bericht ausgeben, oder auch einen Kernel-Panic auslösen (Standard: report). Unabhängig von dieser Option wird die Tag-Prüfung nach dem ersten gemeldeten Fehler deaktiviert.

Wir empfehlen dringend, den SYNC-Modus während der Inbetriebnahme, Entwicklung und Tests zu verwenden. Diese Option sollte global für alle Prozesse aktiviert werden, entweder mit der Umgebungsvariablen oder mit dem Build-System. In diesem Modus werden Fehler frühzeitig 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, den ASYNC-Modus in der Produktion zu verwenden. So haben Sie ein ressourcenschonendes Tool, mit dem Sie Arbeitssicherheitsprobleme in einem Prozess erkennen und die Sicherheit weiter verbessern können. Sobald ein Fehler erkannt wird, kann der Entwickler die Laufzeit-APIs verwenden, um in den SYNC-Modus zu wechseln und einen genauen Stacktrace von einer Stichprobe von Nutzern zu erhalten.

Wir empfehlen dringend, die bevorzugte MTE-Ebene für die CPU für das SoC zu konfigurieren. Der ASYMM-Modus hat in der Regel die gleichen Leistungsmerkmale wie der ASYNC-Modus, und ist ihm fast immer vorzuziehen. Kleine In-Order-Cores zeigen in allen drei Modi oft eine ähnliche Leistung und können so konfiguriert werden, dass sie den SYNC-Modus bevorzugen.

Entwickler sollten auf Abstürze prüfen, indem sie /data/tombstones, logcat überprüfen oder die DropboxManager Pipeline des Anbieters auf Fehler bei Endnutzern überwachen. Weitere Informationen zum Debuggen von nativem Android-Code finden Sie hier.

Plattformkomponenten mit aktiviertem MTE

In Android 12 verwenden eine Reihe sicherheitskritischer Systemkomponenten MTE ASYNC, um Abstürze bei Endnutzern zu erkennen und als zusätzliche Sicherheitsebene zu dienen. Diese Komponenten sind:

  • Netzwerk-Daemons und ‑Dienstprogramme (mit Ausnahme von netd)
  • Bluetooth-, SecureElement-, NFC-HALs und System-Apps
  • statsd-Daemon
  • system_server
  • zygote64 (damit Apps MTE verwenden 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)
  • Prozesse mit nicht vertrauenswürdigen Eingaben (Regel der zwei)
  • Akzeptable Leistungsverlangsamung (die Verlangsamung führt nicht zu einer für Nutzer sichtbaren Latenz)

Wir empfehlen Anbietern, MTE in der Produktion für weitere Komponenten zu aktivieren und dabei die oben genannten Kriterien zu berücksichtigen. 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.

Wir planen, die Liste der Systemkomponenten, für die MTE aktiviert ist, zu erweitern. Dabei orientieren wir uns an den Leistungsmerkmalen zukünftiger Hardware-Designs.