ARM-Speicher-Tagging-Erweiterung (MTE)

Warum MTE?

Fehler bei der Speichersicherheit, also Fehler bei der Verarbeitung des Arbeitsspeichers in nativen Programmiersprachen, sind häufige Codeprobleme. Sie führen zu Sicherheitslücken und Stabilitätsproblemen.

Mit Armv9 wurde die Arm Memory Tagging Extension (MTE) eingeführt, eine Hardwareerweiterung, mit der Sie Fehler in Ihrem nativen Code erkennen können, die nach der kostenlosen Nutzung verwendet werden oder Pufferüberlaufprobleme verursachen.

Support prüfen

Ab Android 13 unterstützen ausgewählte Geräte MTE. Führen Sie den folgenden Befehl aus, um zu prüfen, ob Ihr Gerät mit aktivierter MTE ausgeführt wird:

adb shell grep mte /proc/cpuinfo

Wenn das Ergebnis Features : [...] mte lautet, wird auf Ihrem Gerät MTE aktiviert.

Auf einigen Geräten ist MTE nicht standardmäßig aktiviert, Entwickler können jedoch mit aktiviertem MTE einen Neustart durchführen. Dies ist eine experimentelle Konfiguration, die für den normalen Gebrauch nicht empfohlen wird, da sie die Geräteleistung oder -stabilität beeinträchtigen kann, aber für die App-Entwicklung nützlich sein kann. Wenn Sie auf diesen Modus zugreifen möchten, rufen Sie in der App „Einstellungen“ Entwickleroptionen > Erweiterung „Memory Tagging Extension“ auf. Wenn diese Option nicht verfügbar ist, wird die Aktivierung von MTE auf Ihrem Gerät nicht unterstützt.

MTE-Betriebsmodi

MTE unterstützt zwei Modi: SYNC und ASYNC. Der SYNC-Modus bietet bessere Diagnoseinformationen und ist daher besser für Entwicklungszwecke geeignet, während der ASYNC-Modus eine hohe Leistung bietet, sodass er für freigegebene Anwendungen aktiviert werden kann.

Synchroner Modus (SYNC)

Dieser Modus ist für die Fehlerbehebung optimiert und kann als präzises Tool zur Fehlererkennung verwendet werden, wenn ein höherer Leistungsaufwand akzeptabel ist. Wenn MTE SYNC aktiviert ist, dient auch die Sicherheitsminderung.

Bei einem nicht übereinstimmenden Tag beendet der Prozessor den Prozess für den problematischen Ladevorgang oder die Speicheranweisung mit SIGSEGV (mit si_code SEGV_MTESERR) und vollständigen Informationen über den Arbeitsspeicherzugriff und die fehlerhafte Adresse.

Dieser Modus ist während des Tests als schnellere Alternative zu HWASan nützlich, bei der Sie Ihren Code nicht neu kompilieren müssen, oder in der Produktion, wenn Ihre Anwendung eine anfällige Angriffsfläche darstellt. Wenn der ASYNC-Modus (siehe unten) einen Fehler gefunden hat, kann ein genauer Fehlerbericht abgerufen werden. Dazu verwenden Sie die Laufzeit-APIs, um die Ausführung in den SYNC-Modus zu wechseln.

Darüber hinaus zeichnet der Android-Allocator im SYNC-Modus den Stacktrace jeder Zuweisung und Freigabe auf und verwendet sie, um bessere Fehlerberichte bereitzustellen, die eine Erklärung eines Speicherfehlers (z. B. Use-After-Free oder Pufferüberlauf) und die Stacktraces der relevanten Speicherereignisse enthalten (weitere Informationen finden Sie unter MTE-Berichte). Solche Berichte bieten mehr Kontextinformationen und erleichtern das Nachverfolgen und Beheben von Fehlern als im ASYNC-Modus.

Asynchroner Modus (ASYNC)

Dieser Modus ist im Hinblick auf Leistung statt Genauigkeit von Fehlerberichten optimiert und kann für die Erkennung von Arbeitsspeicher-Sicherheitsfehlern mit geringem Mehraufwand verwendet werden. Bei einem nicht übereinstimmenden Tag setzt der Prozessor die Ausführung bis zum nächsten Kernel-Eintrag (z. B. Systemaufruf oder Timer-Unterbrechung) fort, wo er den Prozess mit SIGSEGV (Code SEGV_MTEAERR) beendet, ohne die fehlerhafte Adresse oder den fehlerhaften Speicherzugriff aufzuzeichnen.

Dieser Modus ist nützlich, um Sicherheitslücken in der Produktionsumgebung auf gut getesteten Codebasen zu minimieren, bei denen die Dichte der sicherheitsrelevanten Programmfehler im Arbeitsspeicher gering ist. Dies wird durch die Verwendung des SYNC-Modus während des Tests erreicht.

MTE aktivieren

Für ein einzelnes Gerät

Zum Testen können Änderungen an der App-Kompatibilität verwendet werden, um den Standardwert des Attributs memtagMode für eine Anwendung festzulegen, die keinen Wert im Manifest oder "default" angibt.

Sie finden sie im globalen Einstellungsmenü unter „System“ > „Erweitert“ > „Entwickleroptionen“ > „Änderungen an der App-Kompatibilität“. Wenn Sie NATIVE_MEMTAG_ASYNC oder NATIVE_MEMTAG_SYNC festlegen, wird MTE für eine bestimmte Anwendung aktiviert.

Alternativ kann dies mit dem Befehl am so festgelegt werden:

  • Für den SYNC-Modus: $ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
  • Für den ASYNC-Modus: $ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name

In Gradle

Sie können MTE für alle Debug-Builds Ihres Gradle-Projekts aktivieren, indem Sie

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application android:memtagMode="sync" tools:replace="android:memtagMode"/>
</manifest>

in app/src/debug/AndroidManifest.xml. Dadurch wird memtagMode des Manifests mit der Synchronisierung für Debug-Builds überschrieben.

Alternativ können Sie MTE für alle Builds eines benutzerdefinierten buildType aktivieren. Erstellen Sie dazu Ihren eigenen buildType und fügen Sie den XML-Code in app/src/<name of buildType>/AndroidManifest.xml ein.

Für ein APK auf einem beliebigen kompatiblen Gerät

MTE ist standardmäßig deaktiviert. Anwendungen, die MTE verwenden möchten, können dies tun, indem Sie android:memtagMode unter dem <application>- oder <process>-Tag im 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 Anwendung verwendeten Prozesse aus. Es kann für einzelne Prozesse überschrieben werden, indem das Tag <process> festgelegt wird.

Mit Instrumentierung erstellen

Wenn Sie MTE wie zuvor erläutert aktivieren, können Fehler durch Speicherbeschädigungen auf dem nativen Heap erkannt werden. Zum Erkennen von Speicherbeschädigungen im Stack muss nicht nur MTE für die Anwendung aktiviert werden, sondern der Code mit Instrumentierung neu erstellt werden. Die resultierende App kann nur auf MTE-fähigen Geräten ausgeführt werden.

So erstellen Sie den nativen Code Ihrer Anwendung (JNI) mit MTE:

NK-Build

In der Datei Application.mk:

APP_CFLAGS := -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag
APP_LDFLAGS := -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag

CMake

Führen Sie für jedes Ziel in der Datei CMakeLists.txt folgende Schritte aus:

target_compile_options(${TARGET} PUBLIC -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag)
target_link_options(${TARGET} PUBLIC -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag)

App ausführen

Verwenden und testen Sie Ihre Anwendung nach der Aktivierung von MTE wie gewohnt. Wenn ein Problem mit der Speichersicherheit erkannt wird, stürzt Ihre App mit einem Tombstone ab, der in etwa so aussieht (beachten Sie SIGSEGV mit SEGV_MTESERR für SYNC oder SEGV_MTEAERR für ASYNC):

pid: 13935, tid: 13935, name: sanitizer-statu  >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0  0000007cd94227cc  x1  0000007cd94227cc  x2  ffffffffffffffd0  x3  0000007fe81919c0
x4  0000007fe8191a10  x5  0000000000000004  x6  0000005400000051  x7  0000008700000021
x8  0800007ae92853a0  x9  0000000000000000  x10 0000007ae9285000  x11 0000000000000030
x12 000000000000000d  x13 0000007cd941c858  x14 0000000000000054  x15 0000000000000000
x16 0000007cd940c0c8  x17 0000007cd93a1030  x18 0000007cdcac6000  x19 0000007fe8191c78
x20 0000005800eee5c4  x21 0000007fe8191c90  x22 0000000000000002  x23 0000000000000000
x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
x28 0000000000000000  x29 0000007fe8191b70
lr  0000005800eee0bc  sp  0000007fe8191b60  pc  0000005800eee0c0  pst 0000000060001000

backtrace:
      #00 pc 00000000000010c0  /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #01 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #02 pc 00000000000019cc  /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000487d8  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)

deallocated by thread 13935:
      #00 pc 000000000004643c  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 00000000000421e4  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 00000000000010b8  /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)

allocated by thread 13935:
      #00 pc 0000000000042020  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 0000000000042394  /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 000000000003cc9c  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #03 pc 00000000000010ac  /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #04 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report

Weitere Informationen finden Sie in der AOSP-Dokumentation unter Informationen zu MTE-Berichten. Sie können auch Fehler in Ihrer App mit Android Studio beheben. Der Debugger stoppt dann an der Zeile, die den ungültigen Speicherzugriff verursacht.

Fortgeschrittene Nutzer: MTE in Ihrem eigenen Allocator verwenden

Wenn Sie MTE für Arbeitsspeicher verwenden möchten, der nicht über die normalen Systemzuordnungen zugewiesen wurde, müssen Sie Ihren Zuweisungsspeicher so ändern, dass Arbeitsspeicher und Zeiger getaggt werden.

Die Seiten für Ihren Allocator müssen mithilfe von PROT_MTE im Flag prot von mmap (oder mprotect) zugewiesen werden.

Alle getaggten Zuweisungen müssen auf 16 Byte ausgerichtet sein, da Tags nur für 16-Byte-Blöcke (auch als Granulen bezeichnet) zugewiesen werden können.

Bevor Sie einen Zeiger zurückgeben, müssen Sie mit der Anweisung IRG ein zufälliges Tag generieren und im Zeiger speichern.

So taggen Sie den zugrunde liegenden Arbeitsspeicher:

  • STG: eine einzelne 16-Byte-Granule taggen
  • ST2G: Zwei 16-Byte-Granulen taggen
  • DC GVA: Tag-Cache-Zeile mit demselben Tag

Alternativ wird mit den folgenden Anweisungen auch der Speicher mit Nullen initialisiert:

  • STZG: mit Tags versehen und mit Null eine einzelne 16-Byte-Granule initialisieren
  • STZ2G: Zwei 16-Byte-Granulen taggen und mit Null initialisieren
  • DC GZVA: Tag und Cache-Zeile mit Nullinitialisierung mit demselben Tag

Beachten Sie, dass diese Anleitung auf älteren CPUs nicht unterstützt wird. Sie müssen sie also bedingt ausführen, wenn MTE aktiviert ist. Sie können prüfen, ob MTE für Ihren Prozess aktiviert ist:

#include <sys/prctl.h>

bool runningWithMte() {
      int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
      return mode != -1 && mode & PR_MTE_TCF_MASK;
}

Die Scudo-Implementierung kann als Referenz hilfreich sein.

Weitere Informationen

Weitere Informationen finden Sie im MTE-Nutzerhandbuch für das Android-Betriebssystem von Arm.