Arm-Erweiterung für Speicher-Tagging

Arm v9 führt die Arm Memory Tagging Extension (MTE) ein, eine Hardwareimplementierung von getaggtem Arbeitsspeicher.

Im Wesentlichen kennzeichnet MTE jede Speicherzuweisung/-entfernung mit zusätzlichen Metadaten. Dabei wird einem Speicherort ein Tag zugewiesen, das dann mit Zeigern verknüpft werden kann, die auf diesen Speicherort verweisen. Während der Laufzeit prüft die CPU bei jedem Laden und Speichern, ob der Zeiger und die Metadaten-Tags übereinstimmen.

In Android 12 kann der Heap-Speicher-Allocator des Kernels und des Nutzerbereichs jede Zuweisung mit Metadaten ergänzen. So lassen sich Use-After-Free- und Buffer-Overflow-Fehler erkennen, die die häufigste Ursache für Speichersicherheitsfehler in unseren Codebases sind.

Betriebsmodi von MTE

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 diese Funktion aktiviert ist, dient MTE SYNC als Sicherheitsmaßnahme. Bei einer Tag-Nichtübereinstimmung beendet der Prozessor die Ausführung sofort und beendet den Prozess mit SIGSEGV (Code SEGV_MTESERR) und vollständigen Informationen zum Speicherzugriff und zur Fehleradresse.

Wir empfehlen, diesen Modus während der Tests 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 mithilfe der Laufzeit-APIs ein genauer Fehlerbericht erstellt werden, indem die Ausführung in den SYNC-Modus gewechselt wird.

Wenn der Android-Allokator im SYNC-Modus ausgeführt wird, zeichnet er Stack-Traces für alle Zuordnungen und Dealokationen auf und verwendet sie, um bessere Fehlerberichte zu erstellen, die eine Erklärung zu einem Arbeitsspeicherfehler wie „Use-After-Free“ oder „Buffer-Overflow“ und die Stack-Traces der relevanten Arbeitsspeicherereignisse enthalten. Solche Berichte enthalten mehr Kontextinformationen und erleichtern das Aufspüren und Beheben von Fehlern.

Asynchroner Modus (ASYNC)

Dieser Modus ist für die Leistung optimiert und nicht für die Genauigkeit von Fehlerberichten. Er kann als Low-Overhead-Erkennung für Speichersicherheitsfehler verwendet werden.
Bei einer Tag-Nichtübereinstimmung führt der Prozessor die Ausführung bis zum nächsten Kerneleintrag fort (z. B. ein Systemaufruf oder Timerunterbrechung), wo er den Prozess mit SIGSEGV (Code SEGV_MTEAERR) beendet, ohne die fehlerhafte Adresse oder den fehlerhaften Speicherzugriff aufzuzeichnen.
Wir empfehlen, diesen Modus in der Produktion auf gut getesteten Codebases zu verwenden, bei denen die Dichte der Speichersicherheitsfehler bekanntlich niedrig ist. Dies wird durch die Verwendung des SYNC-Modus während der Tests erreicht.

Asymmetrischer Modus (ASYMM)

Der asymmetrische MTE-Modus ist eine zusätzliche Funktion in Arm v8.7-A. Er bietet eine synchrone Prüfung bei Speicherlesevorgängen und eine asynchrone Prüfung bei Speicherschreibvorgängen mit einer Leistung, die der des ASYNC-Modus ähnelt. In den meisten Fällen ist dieser Modus eine Verbesserung gegenüber dem ASYNC-Modus. Wir empfehlen, ihn zu verwenden, wann immer er verfügbar ist.

Aus diesem Grund wird der symmetrische Modus in keiner der unten beschriebenen APIs erwähnt. Stattdessen kann das Betriebssystem so konfiguriert werden, dass immer der schiefe Modus verwendet wird, wenn „Asynchron“ angefordert wird. Weitere Informationen finden Sie im Abschnitt „CPU-spezifische bevorzugte MTE-Ebene 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, es sei denn, eine der folgenden Optionen ist für einen bestimmten Prozess festgelegt. Unten finden Sie eine Liste der Komponenten, für die MTE aktiviert ist.

MTE über das Buildsystem aktivieren

Als prozessweite Eigenschaft wird die MTE durch die Buildzeiteinstellung der Hauptausführbaren Datei gesteuert. Mit den folgenden Optionen können Sie diese Einstellung für einzelne ausführbare Dateien oder für ganze Unterverzeichnisse im Quellbaum ändern. Die Einstellung wird für Bibliotheken oder Ziele ignoriert, die weder ausführbar noch ein Test sind.

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

MTE-Modus Einstellung
Asynchrone MTE
  sanitize: {
  memtag_heap: true,
  }
Synchrone 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 mithilfe einer Produktvariablen aktivieren:

MTE-Modus Liste einschließen 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
Synchrone MTE MEMTAG_HEAP_SYNC_INCLUDE_PATHS

oder durch Angabe des Ausschlusspfads einer ausführbaren Datei:

MTE-Modus Einstellung
Asynchrone MTE PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
Synchrone MTE

Beispiel (ähnlich 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 mithilfe von Systemeigenschaften aktivieren

Die oben genannten Build-Einstellungen 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 der asynchronen MTE festlegen möchten, verwenden Sie adb shell setprop arm64.memtag.process.ping async.

MTE mit einer Umgebungsvariablen aktivieren

Sie können die Build-Einstellung auch überschreiben, indem Sie die Umgebungsvariable definieren: MEMTAG_OPTIONS=(off|sync|async). Ist sowohl die Umgebungsvariable als auch die Systemeigenschaft definiert, 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 <application>- oder <process>-Tag in AndroidManifest.xml festlegen.

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

Wenn das Attribut im <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 Sie Kompatibilitätsänderungen verwenden, um den Standardwert des Attributs memtagMode für eine App festzulegen, die im Manifest keinen Wert angibt (oder default angibt).
Diese finden Sie im Menü „Globale Einstellungen“ unter System > Advanced > Developer options > App Compatibility Changes. Wenn Sie NATIVE_MEMTAG_ASYNC oder NATIVE_MEMTAG_SYNC festlegen, wird die MTE für eine bestimmte App aktiviert.
Alternativ können Sie dies mit dem Befehl am so festlegen:

$ 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 des Bring-Ups für alle nativen Binärdateien zu aktivieren. So lassen sich Speichersicherheitsfehler frühzeitig erkennen und eine realistische Abdeckung der Nutzer abbilden, wenn die Funktion in Testbuilds aktiviert ist.

Wir empfehlen dringend, MTE während der Entwicklung in allen nativen Binärdateien im synchronen Modus zu aktivieren.

SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m

Wie jede Variable im Build-System kann SANITIZE_TARGET als Umgebungsvariable oder make-Einstellung verwendet werden, z. B. in einer product.mk -Datei.
Hinweis: Dadurch wird MTE für alle nativen Prozesse aktiviert, aber nicht für Apps, die von zygote64 abgespalten wurden. Für diese Apps kann MTE gemäß der Anleitung oben aktiviert werden.

CPU-spezifische bevorzugte MTE-Ebene konfigurieren

Auf einigen CPUs kann die Leistung von MTE im ASYMM- oder sogar SYNC-Modus der von ASYNC ähneln. Daher ist es sinnvoll, auf diesen CPUs strengere Prüfungen zu aktivieren, wenn ein weniger strenger Prüfmodus angefordert wird, um die Vorteile der Fehlererkennung durch die strengeren Prüfungen ohne Leistungseinbußen zu nutzen.
Prozesse, die für die Ausführung im ASYNC-Modus konfiguriert sind, werden standardmäßig 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 die Wertsynchronisierung beim Starten in den Eintrag sysfs/sys/devices/system/cpu/cpu<N>/mte_tcf_preferred geschrieben werden. Dies kann mit einem Init-Script 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, können Sie der init-Klausel eines Anbieter-Init-Scripts Folgendes hinzufügen:

  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 Stack-Trace des Speicherfehlers. Sie enthalten jedoch keinen Stack-Trace für die Zuweisung oder Deaktivierung. Diese Stack-Traces sind nur verfügbar, wenn der Prozess so konfiguriert ist, dass er im SYNC-Modus ausgeführt wird.

int mallopt(M_THREAD_DISABLE_MEM_INIT, level)

wobei level 0 oder 1 ist.
Deaktiviert die Speicherinitialisierung in malloc und verhindert das Ändern von Speicher-Tags, es sei denn, dies ist für die Korrektheit erforderlich.

int mallopt(M_MEMTAG_TUNING, level)

wobei level Folgendes ist:

  • M_MEMTAG_TUNING_BUFFER_OVERFLOW
  • M_MEMTAG_TUNING_UAF

Hier wird die Tag-Zuweisungsstrategie ausgewählt.

  • Die Standardeinstellung ist M_MEMTAG_TUNING_BUFFER_OVERFLOW.
  • M_MEMTAG_TUNING_BUFFER_OVERFLOW – Ermöglicht die deterministische Erkennung von linearen Pufferüberlauf- und -unterlauf-Fehlern, indem benachbarten Zuweisungen eindeutige Tag-Werte zugewiesen werden. In diesem Modus ist die Wahrscheinlichkeit, dass Fehler vom Typ „Use-After-Free“ erkannt werden, 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 desselben Tag-Granulats (16-Byte-ausgerichteter Block) erkennen kann und auch in diesem Modus kleine Überläufe übersehen werden können. 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 zufällige Tags für eine einheitliche Wahrscheinlichkeit von etwa 93 %, sowohl räumliche (Buffer-Overflow) als auch zeitliche (Use-After-Free) Fehler zu erkennen.

Zusätzlich zu den oben beschriebenen APIs sollten erfahrene Nutzer Folgendes beachten:

  • Durch das Festlegen desPSTATE.TCO-Hardwareregisters kann die Tag-Prüfung vorübergehend unterdrückt werden (Beispiel). Beispielsweise beim Kopieren eines Speicherbereichs mit unbekanntem Tag-Inhalt oder beim Beheben eines Leistungsengpasses in einer Hotloop.
  • Wenn Sie M_HEAP_TAGGING_LEVEL_SYNC verwenden, liefert der System-Absturz-Handler zusätzliche Informationen wie Stack-Traces für die Zuweisung und Deaktivierung. Für diese Funktion ist Zugriff auf die Tag-Bits erforderlich. Sie wird aktiviert, indem beim Festlegen des Signalhandlers das Flag SA_EXPOSE_TAGBITS übergeben wird. Bei allen Programmen, die einen eigenen Signalhandler festlegen und unbekannte Abstürze an das System weiterleiten, wird empfohlen, dasselbe zu tun.

MTE im Kernel

Wenn Sie MTE-gestützte KASAN für den Kernel aktivieren möchten, konfigurieren Sie den Kernel mit CONFIG_KASAN=y, CONFIG_KASAN_HW_TAGS=y. Diese Konfigurationen sind ab Android 12-5.10 standardmäßig in GKI-Kerneln aktiviert.
Dies kann beim Starten mit den folgenden Befehlszeilenargumenten gesteuert werden:

  • kasan=[on|off] – KASAN aktivieren oder deaktivieren (Standard: on)
  • kasan.mode=[sync|async] – Wählen Sie zwischen dem synchronen und dem asynchronen Modus aus (Standard: sync).
  • kasan.stacktrace=[on|off] – Gibt an, ob Stack-Traces erfasst werden sollen (Standard: on).
    • Für die Erfassung des Stack-Traces ist außerdem stack_depot_disable=off erforderlich.
  • kasan.fault=[report|panic] – Gibt an, ob nur der Bericht gedruckt oder auch der Kernel in den Notfallmodus versetzt werden soll (Standard: report). Unabhängig von dieser Option wird die Tag-Prüfung nach dem ersten gemeldeten Fehler deaktiviert.

Wir empfehlen dringend, während der Einrichtung, Entwicklung und Tests den SYNC-Modus 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 Produktionsumgebung den ASYNC-Modus zu verwenden. Dies ist ein Tool mit geringem Overhead, mit dem sich Speichersicherheitsfehler in einem Prozess erkennen lassen, sowie eine weitere Verteidigungsebene. Sobald ein Fehler erkannt wurde, können Entwickler die Laufzeit-APIs verwenden, um in den SYNC-Modus zu wechseln und einen genauen Stack-Trace von einer Stichprobe von Nutzern zu erhalten.

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

Entwickler sollten nach Abstürzen suchen, indem sie /data/tombstones, logcat prüfen 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 wird in einer Reihe von sicherheitskritischen Systemkomponenten MTE ASYNC verwendet, um Abstürze von Endnutzern zu erkennen und als zusätzliche Verteidigungsebene 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 die Verwendung von MTE aktivieren können)

Diese Ziele wurden anhand der folgenden Kriterien ausgewählt:

  • Ein privilegierter Prozess (definiert als ein Prozess, der auf etwas zugreifen kann, auf das die SELinux-Domain „unprivileged_app“ nicht zugreifen kann)
  • Verarbeitet nicht vertrauenswürdige Eingaben (Regel von zwei)
  • Akzeptable Leistungseinbußen (keine für Nutzer sichtbare Latenz)

Wir empfehlen Anbietern, die MTE für weitere Komponenten in der Produktion zu aktivieren und dabei die oben genannten Kriterien zu beachten. Während der Entwicklung empfehlen wir, diese Komponenten im SYNC-Modus zu testen, um leicht zu behebende Fehler zu erkennen und die Auswirkungen von ASYNC auf die Leistung zu bewerten.
In Zukunft wird die Liste der Systemkomponenten, für die MTE aktiviert ist, basierend auf den Leistungsmerkmalen der kommenden Hardwaredesigns erweitert.