Dein Telefon kann Speicherverfälschungen in der Produktion erkennen. Nicht mit der vollständigen Instrumentierung, die du in der CI ausführst, und nicht bei jeder Allokation. Aber die Hardware in deiner Tasche versendet seit einigen Jahren bereits die notwendigen Primitive, und eine wachsende Zahl von Produktions-Apps schaltet sie stillschweigend ein.

Die Kurzfassung: AddressSanitizer ist für die Produktion zu teuer. ARM Memory Tagging Extension nicht. Und wenn du dich noch nicht auf MTE verlassen kannst, bietet dir GWP-ASan probabilistische Abdeckung mit nahezu keinem Overhead. Gemeinsam beantworten sie eine Frage, die früher eine deprimierende Antwort hatte.

Speicherverfälschung ist der Bug, den du auslieferst

Use-after-free, buffer overflow und heap corruption sind die Bugs, die jede Test-Suite bestehen. Sie leben im nativen Code, reproduzieren sich schlecht und stürzen oft irgendwo weit vom eigentlichen Fehler ab. Wenn ein Nutzer einen Crash meldet, ist die ursprüngliche Verfälschung bereits passiert, und dein einziger Beweis ist ein verstümmelter Stack Trace oder ein SIGSEGV an einer Adresse, die zufällig aussieht.

Traditionelle Tools fangen diese früh. Valgrind ist genau, aber langsam. ASan ist schneller, fügt aber immer noch etwa 2-3x Speicher-Overhead und 2x CPU-Overhead hinzu. Das ist in Ordnung für Fuzzing oder Unit-Tests. Es ist nicht in Ordnung für ein batteriebetriebenes Gerät, das Instagram ausführt.

Die Produktion war historisch ein toter Winkel. Entweder hast du den Bug lokal reproduziert oder geraten.

Wie ARM MTE Speicher in Hardware taggt

ARM Memory Tagging Extension, eingeführt in ARMv8.5-A, gibt der CPU eine kostengünstige Möglichkeit, die Gültigkeit von Zeigern bei jedem Speicherzugriff zu überprüfen. Es funktioniert, indem jedem 16-Byte-Granulat des Speichers ein 4-Bit-Tag zugewiesen und ein passendes Tag im oberen Byte des Zeigers gespeichert wird.

Bei jedem Load oder Store vergleicht die CPU das Zeiger-Tag mit dem Speicher-Tag. Wenn sie sich unterscheiden, löst die Hardware einen Fehler aus. Das ist kein Software-Check. Es passiert im Speicher-Subsystem, und der Overhead liegt typischerweise unter 5 %.

Das obere Byte eines 64-Bit-Zeigers wurde von der Hardware bereits ignoriert. MTE nutzt diese Bits neu. Ein Zeiger wie 0xb7f0_0000_1234_5678 trägt das Tag 0xb7. Der Speicher an dieser Adresse muss dasselbe Tag tragen, sonst führt der Zugriff zu einem Fehler.

MTE ist auf Geräten mit ARMv8.5-A oder später verfügbar. Dazu gehören das Google Pixel 8 und später, das iPhone 15 Pro und später und ein wachsender Anteil von Mittelklasse-Android-Geräten. Es ist nicht universell, aber es ist nicht mehr exotisch.

Der Kernel stellt MTE über prctl-Flags bereit. Eine Anwendung kann synchrones oder asynchrones Prüfen anfordern. Der synchrone Modus löst sofort bei Tag-Mismatch einen Fehler aus. Der asynchrone Modus reiht den Fehler ein und liefert ihn später aus, was günstiger ist, aber die Erkennung leicht verzögert.

GWP-ASan: Wenn du nicht alles taggen kannst

MTE ist großartig, aber es erfordert kompatible Hardware und getaggte Allokationen in deiner gesamten Codebase. Wenn du auf älteren Geräten auslieferst oder wenn du nur gezielten Schutz möchtest, ist GWP-ASan die pragmatische Alternative.

GWP-ASan steht für Guarded Write Protection AddressSanitizer. Es ist ein Sampling-Allocator, der einen kleinen Bruchteil der Heap-Allokationen in bewachte Seiten platziert, die von vergifteten Redzones umgeben sind. Wenn ein use-after-free oder buffer overflow diese Redzones berührt, löst die Hardware-MMU sofort einen Fehler aus.

Der Schlüsselgedanke ist Wahrscheinlichkeit. GWP-ASan könnte 1 von 10.000 Allokationen bewachen. Das klingt nutzlos, bis dir klar wird, dass eine fehlerhafte App das Speicher tausende Male beschädigt, bevor sie abstürzt. Bei genügend Nutzer-Sessions fängt sogar eine Sampling-Rate von 0,01 % echte Bugs in der Produktion.

Google hat GWP-ASan jahrelang in Chrome und Android-Systemdiensten betrieben. Es hat hunderte von use-after-free-Bugs gefunden, die sowohl Testing als auch Fuzzing entkommen sind. Der CPU-Overhead ist vernachlässigbar, weil nur ein winziger Bruchteil der Allokationen den bewachten Pfad durchläuft. Der Speicher-Overhead ist begrenzt, weil der bewachte Pool klein ist und Allokationen schließlich recycelt werden.

Die Kompromisse, über die niemand sprechen will

MTE und GWP-ASan sind nicht kostenlos. Sie sind nur billig genug, um es wert zu sein.

MTE erfordert das obere Byte jedes Zeigers, was bedeutet, dass deine Codebase mit -fsanitize=memtag oder -march=armv8.5-a+memtag kompiliert werden muss. Wenn du Code hast, der Zeiger maskiert, hasht oder sie ohne korrektes Tagging durch JNI leitet, wirst du False Positives bekommen. Das Android NDK und moderne libc-Implementierungen behandeln dies korrekt, aber Custom Allocators oder Pointer-Packing-Schemata werden brechen.

MTE fängt außerdem nur Bugs in den Heap- und Stack-Regionen, die du explizit taggst. Ein Wild Pointer, der in eine nicht-getaggte mmap-Region springt, wird nicht erkannt. Das ist besser als nichts, aber es ist keine totale Abdeckung.

GWP-ASan hat das gegenteilige Problem. Es fängt nur die Allokationen, die zufällig im bewachten Pool landen. Ein Bug, der eine nicht-bewachte Allokation beschädigt, wird unbemerkt bleiben. Du zahlst außerdem einen kleinen Latenz-Kosten bei Allokation und Deallokation für die gesampelte Menge, und du musst die Crashes elegant in deiner Telemetry-Pipeline behandeln.

Keines der beiden Tools ersetzt ASan in deiner CI-Pipeline. Sie ergänzen es. ASan gibt dir deterministische, hochabgedeckte Erkennung während des Testens. MTE und GWP-ASan geben dir ein Sicherheitsnetz im Feld.

MTE auf einem modernen Android-Gerät aktivieren

Wenn du ein Pixel 8 oder neuer hast, kannst du MTE heute testen. Das Android NDK unterstützt es mit einem einzigen Compiler-Flag.

Zuerst prüfst du, ob dein Gerät MTE unterstützt:

#include <sys/prctl.h>
#include <linux/prctl.h>
#include <sys/auxv.h>
#include <asm/hwcap.h>

bool mte_supported() {
    unsigned long hwcap2 = getauxval(AT_HWCAP2);
    return (hwcap2 & HWCAP2_MTE) != 0;
}

Dann baust du deine native Bibliothek mit MTE aktiviert in deiner CMakeLists.txt:

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memtag")

Wenn du synchrones Faulting möchtest, was du typischerweise in der Produktion willst, fragst du es beim Start an:

#include <sys/prctl.h>

void enable_mte() {
    if (mte_supported()) {
        prctl(PR_SET_TAGGED_ADDR_CTRL,
              PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | (0xfffe << PR_MTE_TAG_SHIFT),
              0, 0, 0);
    }
}

Das PR_MTE_TCF_SYNC-Flag sagt dem Kernel, sofort bei Tag-Mismatch zu fehlern. Die Tag-Maske 0xfffe schließt Tag Null aus, das noch von einigem Systemcode verwendet wird.

GWP-ASan für die Produktion einrichten

GWP-ASan ist einfacher zu aktivieren, weil es keine spezielle Hardware benötigt. Auf Android kannst du gegen den GWP-ASan-Allocator-Wrapper linken oder es über den System-Allocator auf neueren API-Levels aktivieren.

Für eine minimale Integration wrappst du deine Allocator-Entry-Points:

extern "C" void* malloc(size_t size) {
    if (__gwp_asan_sample()) {
        return __gwp_asan_guarded_malloc(size);
    }
    return __libc_malloc(size);
}

In der Praxis wirst du die Android GWP-ASan-Runtime oder die LLVM compiler-rt-Implementierung verwenden. Der wichtigste Konfigurations-Regler ist die Sample Rate. Starte konservativ:

// Eine bewachte Allokation pro 10.000
__gwp_asan_set_sample_rate(10000);

Sammle die resultierenden Crashes durch deine bestehende Telemetry. Ein GWP-ASan-Crash sieht wie ein Standard-SIGSEGV aus, aber die faulting Adresse wird in einer bewachten Seite landen. Dein Crash Reporter kann dies erkennen, indem er prüft, ob die faulting Adresse innerhalb des GWP-ASan-Pools liegt.

Was du tatsächlich tun solltest

Wenn du nativen Code auf Mobile auslieferst, solltest du ASan bereits in der CI und beim Fuzzing ausführen. Die Frage ist, was danach passiert.

Wenn dein minimal unterstütztes Gerät ARMv8.5-A-Chips enthält, aktiviere MTE im synchronen Modus. Der Overhead ist so niedrig, dass Nutzer es nicht bemerken werden, und die Crashes, die du bekommst, werden genaue Tag-Mismatch-Informationen anstelle von zufälliger Heap-Verfälschung haben.

Wenn du ältere Geräte unterstützt, aktiviere GWP-ASan mit einer konservativen Sample Rate. Es wird nicht jeden Bug fangen, aber es wird Bugs fangen, die nichts anderes fängt. Über Millionen Sessions hinweg, das ist nicht theoretisch. So hat Chrome letztes Jahr einen use-after-free in seinem Image Decoder gefunden.

Die Hardware in deinem Telefon ist bereits in der Lage, die Speichersicherheits-Bugs zu fangen, vor denen du dich am meisten fürchtest. Die einzige Frage ist, ob du sie eingeschaltet hast.