ARM-Speicher-Tagging-Erweiterung (MTE)

Warum MTE?

Speichersicherheitsfehler, also Fehler bei der Verwaltung von Speicher 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 Use-After-Free- und Pufferüberlauffehler in Ihrem nativen Code erkennen können.

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 auf Ihrem Gerät MTE aktiviert ist:

adb shell grep mte /proc/cpuinfo

Wenn das Ergebnis Features : [...] mte ist, wird auf Ihrem Gerät MTE ausgeführt.

Auf einigen Geräten ist MTE standardmäßig nicht aktiviert, Entwickler können das Gerät aber mit aktiviertem MTE neu starten. 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. Sie kann jedoch für die App-Entwicklung nützlich sein. Rufen Sie in den Einstellungen Entwickleroptionen > „Memory Tagging Extension“ auf, um auf diesen Modus zuzugreifen. Wenn diese Option nicht angezeigt wird, unterstützt Ihr Gerät die Aktivierung von MTE auf diese Weise nicht.

Geräte mit MTE-Unterstützung

Die folgenden Geräte unterstützen MTE:

  • Google Pixel 8 (Shiba)
  • Google Pixel 8 Pro (Husky)
  • Google Pixel 8a (Akita)
  • Google Pixel 9 (Tokay)
  • Google Pixel 9 Pro (Caiman)
  • Google Pixel 9 Pro XL (Komodo)
  • Google Pixel 9 Pro Fold (Comet)
  • Google Pixel 9a (Tegu)

Betriebsmodi von MTE

MTE unterstützt zwei Modi: SYNC und ASYNC. Der SYNC-Modus bietet bessere Diagnoseinformationen und eignet sich daher besser für Entwicklungszwecke. Der ASYNC-Modus bietet eine hohe Leistung und kann daher für veröffentlichte Apps aktiviert werden.

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 es auch als Sicherheitsmaßnahme.

Bei einer Tag-Nichtübereinstimmung beendet der Prozessor den Prozess mit der fehlerhaften Lade- oder Speicheranweisung mit SIGSEGV (mit si_code SEGV_MTESERR) und vollständigen Informationen zum Speicherzugriff und zur Fehleradresse.

Dieser Modus ist während der Tests eine schnellere Alternative zu HWASan, bei der Sie Ihren Code nicht neu kompilieren müssen. Er eignet sich auch für die Produktion, wenn Ihre App eine anfällige Angriffsfläche darstellt. Wenn im ASYNC-Modus (siehe unten) ein Fehler gefunden wurde, kann mithilfe der Laufzeit-APIs ein genauer Fehlerbericht erstellt werden, indem die Ausführung in den SYNC-Modus gewechselt wird.

Außerdem zeichnet der Android-Allokator im SYNC-Modus den Stack-Trace jeder Zuweisung und Freigabe auf und verwendet diese, um bessere Fehlerberichte zu erstellen, die eine Erklärung zu einem Arbeitsspeicherfehler wie Use-After-Free oder Pufferüberlauf und die Stack-Traces der relevanten Arbeitsspeicherereignisse enthalten. Weitere Informationen finden Sie unter MTE-Berichte verstehen. Solche Berichte enthalten mehr Kontextinformationen und erleichtern das Aufspüren und Beheben von Fehlern im Vergleich zum ASYNC-Modus.

Asynchroner Modus (ASYNC)

Dieser Modus ist für die Leistung optimiert und nicht für die Genauigkeit von Fehlerberichten. Er kann für die Erkennung von Speichersicherheitsfehlern mit geringem Overhead 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 Speicherzugriff aufzuzeichnen.

Dieser Modus ist nützlich, um Speichersicherheitslücken in der Produktion bei gut getesteten Codebases zu minimieren, bei denen die Dichte der Speichersicherheitsfehler bekanntlich gering ist. Dies wird durch die Verwendung des SYNC-Modus während der Tests erreicht.

MTE aktivieren

Für ein einzelnes Gerät

Für Tests können Änderungen an der App-Kompatibilität verwendet werden, um den Standardwert des Attributs memtagMode für eine Anwendung festzulegen, für die im Manifest kein Wert angegeben ist (oder "default").

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

Alternativ können Sie dies mit dem Befehl am so festlegen:

  • 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 die memtagMode Ihres Manifests für Debug-Builds überschrieben.

Alternativ können Sie MTE für alle Builds eines benutzerdefinierten BuildType aktivieren. Erstellen Sie dazu einen eigenen buildType und legen Sie die XML-Datei in app/src/<name of buildType>/AndroidManifest.xml ab.

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

MTE ist standardmäßig deaktiviert. Wenn Sie MTE in Apps verwenden möchten, können Sie android:memtagMode unter dem <application>- oder <process>-Tag in der AndroidManifest.xml festlegen.

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

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

Mit Instrumentierung erstellen

Wenn Sie MTE wie oben beschrieben aktivieren, können Sie Fehler bei der Beschädigung des Arbeitsspeichers im nativen Heap erkennen. Um Speicherbeschädigungen im Stack zu erkennen, müssen Sie nicht nur die MTE für die App aktivieren, sondern auch den Code mit Instrumentierung neu erstellen. Die resultierende App kann nur auf MTE-kompatiblen Geräten ausgeführt werden.

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

ndk-build

In Ihrer Application.mk-Datei:

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ür jedes Ziel in CMakeLists.txt:

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

Nachdem Sie die MTE aktiviert haben, können Sie Ihre App wie gewohnt verwenden und testen. Wenn ein Problem mit der Speichersicherheit erkannt wird, stürzt Ihre App mit einem Tombstone ab, der in etwa so aussieht (beachten Sie die 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 MTE-Berichte verstehen. Sie können auch Ihre App mit Android Studio debuggen. Der Debugger hält an der Zeile an, die den ungültigen Speicherzugriff verursacht.

Fortgeschrittene Nutzer: MTE in Ihrem eigenen Allocator verwenden

Wenn Sie MTE für Speicher verwenden möchten, der nicht über die normalen Systemallokatoren zugewiesen wird, müssen Sie Ihren Allokator so ändern, dass Speicher und Zeiger getaggt werden.

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

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

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

So taggen Sie den zugrunde liegenden Arbeitsspeicher:

  • STG: Taggt ein einzelnes 16-Byte-Granul.
  • ST2G: zwei 16-Byte-Granulate taggen
  • DC GVA: Cachezeile mit demselben Tag taggen

Alternativ können Sie den Speicher auch mit der folgenden Anleitung auf Null initialisieren:

  • STZG: Taggen und Nullinitialisieren eines einzelnen 16-Byte-Granels
  • STZ2G: Zwei 16-Byte-Granulate taggen und auf Null initialisieren
  • DC GZVA: Cachezeile mit demselben Tag taggen und auf Null initialisieren

Diese Anleitung wird auf älteren CPUs nicht unterstützt. Sie müssen sie also bedingt ausführen, wenn MTE aktiviert ist. So prüfen Sie, ob die 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;
}

Als Referenz kann die scudo-Implementierung hilfreich sein.

Weitere Informationen

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