ARM-Tagging-Erweiterung für Arbeitsspeicher

Arm v9 führt Arm Memory Tagging Extension (MTE) ein, eine Hardware-Implementierung von Tagged Memory.

Auf hoher Ebene kennzeichnet MTE jede Speicherzuweisung/-freigabe mit zusätzlichen Metadaten. Es weist einem Speicherort ein Tag zu, das dann mit Zeigern verknüpft werden kann, die auf diesen Speicherort verweisen. Zur Laufzeit prüft die CPU bei jedem Laden und Speichern, ob der Zeiger und die Metadaten-Tags übereinstimmen.

In Android 12 kann der Kernel- und Userspace-Heap-Speicherzuordner jede Zuweisung mit Metadaten erweitern. Dies hilft dabei, Use-After-Free- und Buffer-Overflow-Fehler zu erkennen, die die häufigste Ursache für Speichersicherheitsfehler in unseren Codebasen sind.

MTE-Betriebsarten

MTE verfügt über drei Betriebsmodi:

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

Synchronmodus (SYNC)

Dieser Modus ist hinsichtlich der Korrektheit der Fehlererkennung gegenüber der Leistung optimiert und kann als präzises Fehlererkennungstool 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 über den Speicherzugriff und die fehlerhafte Adresse.

Wir empfehlen die Verwendung dieses Modus während des Testens als Alternative zu HWASan/KASAN oder in der Produktion, wenn der Zielprozess eine anfällige Angriffsfläche darstellt. Wenn der ASYNC-Modus außerdem das Vorhandensein eines Fehlers angezeigt hat, kann ein genauer Fehlerbericht erstellt werden, indem die Ausführung mithilfe der Laufzeit-APIs in den SYNC-Modus umgeschaltet wird.

Bei der Ausführung im SYNC-Modus zeichnet der Android-Allocator Stack-Traces für alle Zuweisungen und Freigaben auf und verwendet sie, um bessere Fehlerberichte bereitzustellen, die eine Erklärung eines Speicherfehlers, wie z. B. Use-After-Free oder Buffer-Overflow, und des Stacks enthalten Spuren der relevanten Erinnerungsereignisse. Solche Berichte liefern mehr Kontextinformationen und erleichtern die Verfolgung und Behebung von Fehlern.

Asynchroner Modus (ASYNC)

Dieser Modus ist hinsichtlich der Leistung gegenüber der Genauigkeit von Fehlerberichten optimiert und kann zur Erkennung von Speichersicherheitsfehlern mit geringem Overhead verwendet werden.
Bei einer Tag-Nichtübereinstimmung setzt der Prozessor die Ausführung bis zum nächsten Kernel-Eintrag (z. B. einem Systemaufruf oder einem Timer-Interrupt) fort und beendet dort den Prozess mit SIGSEGV (Code SEGV_MTEAERR ), ohne die fehlerhafte Adresse oder den Speicherzugriff aufzuzeichnen.
Wir empfehlen, diesen Modus in der Produktion auf gut getesteten Codebasen 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)

Als zusätzliche Funktion in Arm v8.7-A bietet der asymmetrische MTE-Modus eine synchrone Überprüfung von Speicherlesevorgängen und eine asynchrone Überprüfung von Speicherschreibvorgängen mit einer ähnlichen Leistung wie der ASYNC-Modus. In den meisten Situationen stellt dieser Modus eine Verbesserung gegenüber dem ASYNC-Modus dar und wir empfehlen, ihn anstelle von ASYNC zu verwenden, wann immer er verfügbar ist.

Aus diesem Grund erwähnt keine der unten beschriebenen APIs den asymmetrischen Modus. Stattdessen kann das Betriebssystem so konfiguriert werden, dass es immer den asymmetrischen Modus verwendet, wenn Asynchron angefordert wird. Weitere Informationen finden Sie im Abschnitt „Konfigurieren der CPU-spezifischen bevorzugten MTE-Ebene“.

MTE im Userspace

In den folgenden Abschnitten wird beschrieben, wie MTE für Systemprozesse und Anwendungen 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).

Aktivieren von MTE mithilfe des Build-Systems

Als prozessweite Eigenschaft wird MTE durch die Buildzeiteinstellung der ausführbaren Hauptdatei 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 alle Ziele ignoriert, die weder ausführbar noch ein Test sind.

1. Aktivieren von MTE in Android.bp ( Beispiel ) 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. Aktivieren von MTE in einem Unterverzeichnis im Quellbaum mithilfe einer Produktvariablen:

MTE-Modus Liste einschließen Ausschlussliste
asynchron PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS MEMTAG_HEAP_ASYNC_INCLUDE_PATHS PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
synchronisieren 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 durch Angabe des Ausschlusspfads einer ausführbaren Datei:

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

Aktivieren von MTE mithilfe von Systemeigenschaften

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ührbaren Datei.

Um beispielsweise /system/bin/ping oder /data/local/tmp/ping für die Verwendung von asynchronem MTE festzulegen, verwenden Sie adb shell setprop arm64.memtag.process.ping async .

Aktivieren von MTE mithilfe einer Umgebungsvariablen

Eine weitere Möglichkeit, die Build-Einstellung 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 Anwendungen aktivieren

Wenn nicht angegeben, ist MTE standardmäßig deaktiviert, aber Apps, die MTE verwenden möchten, können dies tun, indem sie android:memtagMode unter dem Tag <application> oder <process> in 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 Anwendung verwendeten Prozesse aus und kann für einzelne Prozesse durch Festlegen des <process> -Tags überschrieben werden.

Für Experimente können Kompatibilitätsänderungen verwendet werden, um den Standardwert des memtagMode Attributs für eine Anwendung festzulegen, die keinen Wert im Manifest angibt (oder default angibt).
Diese finden Sie unter System > Advanced > Developer options > App Compatibility Changes im globalen Einstellungsmenü. Durch das Festlegen NATIVE_MEMTAG_ASYNC oder NATIVE_MEMTAG_SYNC wird MTE für eine bestimmte Anwendung aktiviert.
Alternativ kann dies mit dem Befehl am wie folgt eingestellt werden:

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

Erstellen eines MTE-System-Images

Wir empfehlen dringend, MTE für alle nativen Binärdateien während der Entwicklung und Bereitstellung zu aktivieren. Dies trägt dazu bei, Speichersicherheitsfehler frühzeitig zu erkennen und bietet eine realistische Benutzerabdeckung, wenn es in Test-Builds aktiviert ist.

Wir empfehlen dringend, MTE während der Entwicklung für alle 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 Datei product.mk ).
Bitte beachten Sie, dass dadurch MTE für alle nativen Prozesse aktiviert wird, jedoch nicht für Anwendungen (die von zygote64 abgeleitet sind), für die MTE gemäß den obigen Anweisungen aktiviert werden kann.

Konfigurieren des CPU-spezifischen bevorzugten MTE-Levels

Auf einigen CPUs kann die Leistung von MTE im ASYMM- oder sogar SYNC-Modus der von ASYNC ähneln. Daher lohnt es sich, strengere Prüfungen für diese CPUs zu aktivieren, wenn ein weniger strenger Prüfmodus angefordert wird, um die Fehlererkennungsvorteile der strengeren Prüfungen ohne Leistungseinbußen zu nutzen.
Standardmäßig werden Prozesse, die für die Ausführung im ASYNC-Modus konfiguriert sind, auf allen CPUs im ASYNC-Modus ausgeführt. Um den Kernel so zu konfigurieren, dass er diese Prozesse im SYNC-Modus auf bestimmten CPUs ausführt, 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 Init-Skript erfolgen. Um beispielsweise die CPUs 0–1 für die Ausführung von Prozessen im ASYNC-Modus im SYNC-Modus und die CPUs 2–3 für die Ausführung im ASYMM-Modus zu konfigurieren, kann Folgendes zur Init-Klausel eines Hersteller-Init-Skripts 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 eine genaue Stapelverfolgung der Position des Speicherfehlers. Sie enthalten jedoch keinen Stack-Trace für die Zuweisung oder Aufhebung der Zuweisung. Diese Stack-Traces 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)

wobei level 0 oder 1 ist.
Deaktiviert die Speicherinitialisierung in malloc und vermeidet das Ändern von Speicher-Tags, es sei denn, dies ist aus Gründen der Korrektheit erforderlich.

int mallopt(M_MEMTAG_TUNING, level)

wobei level ist:

  • M_MEMTAG_TUNING_BUFFER_OVERFLOW
  • M_MEMTAG_TUNING_UAF

Wählt die Tag-Zuweisungsstrategie 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 Zuordnungen unterschiedliche Tag-Werte zugewiesen werden. In diesem Modus ist die Chance, Use-After-Free-Bugs zu erkennen, etwas geringer, da für jeden Speicherort nur die Hälfte der möglichen Tag-Werte verfügbar ist. Bitte beachten Sie, dass MTE keinen Überlauf innerhalb desselben Tag-Granulats (ausgerichteter 16-Byte-Block) erkennen kann und selbst in diesem Modus kleine Überläufe übersehen kann. Ein solcher Überlauf kann nicht die Ursache für eine Speicherbeschädigung sein, da der Speicher 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 (Verwendung nach freier Nutzung) Fehler zu erkennen.

Zusätzlich zu den oben beschriebenen APIs sollten erfahrene Benutzer möglicherweise Folgendes beachten:

  • Durch Festlegen des Hardwareregisters PSTATE.TCO kann die Tag-Prüfung vorübergehend unterdrückt werden ( Beispiel ). Zum Beispiel beim Kopieren eines Speicherbereichs mit unbekanntem Tag-Inhalt oder beim Beheben eines Leistungsengpasses in einer Hot-Loop.
  • Bei Verwendung von M_HEAP_TAGGING_LEVEL_SYNC stellt der Systemabsturz-Handler zusätzliche Informationen wie Zuordnungs- und Freigabe-Stack-Traces bereit. Diese Funktionalität erfordert Zugriff auf die Tag-Bits und wird durch Übergabe des Flags SA_EXPOSE_TAGBITS beim Festlegen des Signalhandlers aktiviert. Wir empfehlen jedem Programm, das seinen eigenen Signalhandler einrichtet und unbekannte Abstürze an den Systemhandler delegiert, dasselbe 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 standardmäßig auf GKI-Kerneln aktiviert, beginnend mit Android 12-5.10 .
Dies kann beim Booten mit den folgenden Befehlszeilenargumenten gesteuert werden:

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

Wir empfehlen dringend, beim Hochfahren, Entwickeln und Testen den SYNC-Modus zu verwenden. Diese Option sollte global für alle Prozesse aktiviert werden, die die Umgebungsvariable oder mit dem Build-System verwenden. 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. Dies bietet ein Tool mit geringem Overhead zum Erkennen von Speichersicherheitsfehlern in einem Prozess sowie eine weitere Tiefenverteidigung. Sobald ein Fehler erkannt wird, kann der Entwickler die Laufzeit-APIs nutzen, um in den SYNC-Modus zu wechseln und einen genauen Stack-Trace von einer Stichprobe von Benutzern zu erhalten.

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

Entwickler sollten das Vorhandensein von Abstürzen überprüfen, indem sie /data/tombstones oder logcat überprüfen oder die DropboxManager Pipeline des Anbieters auf Endbenutzerfehler überwachen. Weitere Informationen zum Debuggen von nativem Android-Code finden Sie hier .

MTE-fähige Plattformkomponenten

In Android 12 verwenden eine Reihe sicherheitskritischer Systemkomponenten MTE ASYNC, um Endbenutzerabstürze zu erkennen und als zusätzliche Ebene der Tiefenverteidigung zu fungieren. Diese Komponenten sind:

  • Netzwerk-Daemons und Dienstprogramme (mit Ausnahme von netd )
  • Bluetooth, SecureElement, NFC-HALs und Systemanwendungen
  • statsd Daemon
  • system_server
  • zygote64 (um Anwendungen zu ermöglichen, sich für die Verwendung von MTE zu entscheiden)

Diese Ziele wurden anhand der folgenden Kriterien ausgewählt:

  • Ein privilegierter Prozess (definiert als ein Prozess, der Zugriff auf etwas hat, was die SELinux-Domäne unprivileged_app nicht hat)
  • Verarbeitet nicht vertrauenswürdige Eingaben ( Zweierregel )
  • Akzeptable Leistungsverlangsamung (die Verlangsamung führt nicht zu einer für den Benutzer sichtbaren Latenz)

Wir ermutigen Anbieter, MTE in der Produktion für mehr Komponenten zu aktivieren und dabei die oben genannten Kriterien zu befolgen. 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 ihre Leistung zu bewerten.
In Zukunft plant Android, die Liste der Systemkomponenten, auf denen MTE aktiviert ist, zu erweitern, basierend auf den Leistungsmerkmalen kommender Hardwaredesigns.