Arm-Erweiterung für Speicher-Tagging

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-RegistersPSTATE.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 Flag SA_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.
  • 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.

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-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 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.