SIGSEGV-Abstürze mit Code 9 (SEGV_MTESERR) oder Code 8 (SEGV_MTEAERR) sind Fehler beim Speicher-Tagging. Memory Tagging Extension (MTE) ist eine Armv9-Funktion, die in Android 12 und höher unterstützt wird. MTE ist eine Hardwareimplementierung von getaggtem Speicher. Sie bietet einen detaillierten Arbeitsspeicherschutz zur Erkennung und Behebung von Fehlern bei der Arbeitsspeichersicherheit.
In C/C++ kann ein Zeiger, der von einem Aufruf von malloc() oder operator new() oder ähnlichen Funktionen zurückgegeben wird, nur zum Zugriff auf den Speicher innerhalb der Grenzen dieser Zuweisung und nur verwendet werden, solange die Zuweisung aktiv ist (nicht freigegeben oder gelöscht wurde). MTE wird in Android verwendet, um Verstöße gegen diese Regel zu erkennen, die in den Absturzberichten als „Buffer Overflow“/„Buffer Underflow“ und „Use After Free“ bezeichnet werden.
MTE hat zwei Modi: synchron (oder „sync“) und asynchron (oder „async“). Ersteres ist langsamer, liefert aber genauere Diagnosen. Letztere ist schneller, kann aber nur ungefähre Details liefern. Wir werden beide einzeln behandeln, da die Diagnosen etwas unterschiedlich sind.
MTE im synchronen Modus
Im synchronen („sync“) Modus von MTE stürzt SIGSEGV mit dem Code 9 (SEGV_MTESERR) ab.
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)
Alle MTE-Absturzberichte enthalten den üblichen Registerdump und Backtrace für den Punkt, an dem das Problem erkannt wurde. Die Zeile „Ursache:“ für einen von MTE erkannten Fehler enthält wie im Beispiel oben „[MTE]“ sowie weitere Details. In diesem Fall wurde die Fehlerart „Verwendung nach Freigabe“ erkannt. „0 Byte in einer 32-Byte-Zuweisung bei 0x7ae92853a0“ gibt die Größe und Adresse der Zuweisung sowie den Offset in der Zuweisung an, auf den wir zugegriffen haben.
MTE-Absturzberichte enthalten auch zusätzliche Backtraces, nicht nur den vom Zeitpunkt der Erkennung.
Bei „Use After Free“-Fehlern werden dem Crash-Dump die Abschnitte „Deallocated by“ (Von wem deaktiviert) und „Allocated by“ (Von wem zugewiesen) hinzugefügt. Sie enthalten die Stack-Traces zum Zeitpunkt der Deaktivierung dieses Speichers (bevor er verwendet wurde) und der vorherigen Zuweisung. Außerdem sehen Sie, welcher Thread die Zuweisung bzw. Deaktivierung vorgenommen hat. In diesem einfachen Beispiel sind alle drei Threads (Erkennungs-, Zuweisungs- und Deaktivierungs-Thread) identisch. In komplexeren realen Fällen ist das aber nicht unbedingt der Fall. Wenn Sie wissen, dass sie sich unterscheiden, kann dies ein wichtiger Hinweis auf einen fehlerhaften Thread sein.
Bei „Buffer Overflow“- und „Buffer Underflow“-Fehlern wird nur eine zusätzliche Stack-Spalte „allocated by“ (von wem zugewiesen) angegeben, da sie per Definition noch nicht deallociert wurden (andernfalls würden sie als „Use After Free“ angezeigt):
Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0 [...] backtrace: [...] allocated by thread 13949:
Beachten Sie die Verwendung des Wortes „rechts“ hier: Das bedeutet, dass wir Ihnen mitteilen, wie viele Byte nach dem Ende der Zuweisung der falsche Zugriff war. Bei einem Unterlauf würde „links“ angegeben und es würde sich um eine Anzahl von Byte vor dem Beginn der Zuweisung handeln.
Mehrere mögliche Ursachen
Manchmal enthalten SEGV_MTESERR-Berichte die folgende Zeile:
Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.
Das passiert, wenn es mehrere gute Kandidaten für die Fehlerquelle gibt und wir nicht sagen können, was die tatsächliche Ursache ist. Wir drucken bis zu drei solcher Kandidaten in ungefährer Wahrscheinlichkeitsreihenfolge aus und überlassen die Analyse dem Nutzer.
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x400007b43063db5 backtrace: [stack...] Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability. Cause: [MTE]: Use After Free, 5 bytes into a 10-byte allocation at 0x7b43063db0 deallocated by thread 6663: [stack...] allocated by thread 6663: [stack...] Cause: [MTE]: Use After Free, 5 bytes into a 6-byte allocation at 0x7b43063db0 deallocated by thread 6663: [stack...] allocated by thread 6663: [stack...]
Im obigen Beispiel wurden zwei aktuelle Zuweisungen an derselben Speicheradresse erkannt, die das beabsichtigte Ziel des ungültigen Speicherzugriffs gewesen sein könnten. Das kann passieren, wenn kostenloser Speicher bei Zuweisungen wiederverwendet wird, z. B. bei der Sequenz „neu, kostenlos, neu, kostenlos, neu, kostenlos, Zugriff“. Die aktuellere Zuweisung wird zuerst gedruckt.
Heuristische Verfahren zur detaillierten Ursachebestimmung
Unter „Ursache“ eines Absturzes sollte die Speicherzuweisung angezeigt werden, aus der der zugegriffene Zeiger ursprünglich abgeleitet wurde. Leider kann MTE-Hardware einen Verweis mit einem nicht übereinstimmenden Tag nicht in eine Zuweisung umwandeln. Um einen SEGV_MTESERR-Absturz zu erklären, analysiert Android die folgenden Daten:
- Die Fehleradresse (einschließlich des Pointer-Tags).
- Eine Liste der letzten Heap-Zuordnungen mit Stack-Traces und Speicher-Tags.
- Aktuelle (Live-)Zuweisungen in der Nähe und ihre Speicher-Tags.
Jeder kürzlich deallocierte Speicher an der Fehleradresse, bei dem das Speicher-Tag mit dem Tag der Fehleradresse übereinstimmt, ist eine potenzielle Ursache für „Use After Free“.
Jeder in der Nähe befindliche Arbeitsspeicher, dessen Speicher-Tag mit dem Fehleradressen-Tag übereinstimmt, ist eine potenzielle Ursache für einen „Buffer Overflow“ (oder „Buffer Underflow“).
Zuordnungen, die der Fehlerquelle näher sind – entweder zeitlich oder räumlich –, werden als wahrscheinlicher eingestuft als solche, die weit entfernt sind.
Da nicht mehr zugewiesener Arbeitsspeicher häufig wiederverwendet wird und die Anzahl der verschiedenen Tag-Werte gering ist (weniger als 16), ist es nicht ungewöhnlich, dass mehrere wahrscheinliche Kandidaten gefunden werden. Es gibt keine Möglichkeit, die tatsächliche Ursache automatisch zu ermitteln. Aus diesem Grund werden in MTE-Berichten manchmal mehrere mögliche Ursachen aufgeführt.
Es wird empfohlen, dass der App-Entwickler sich die möglichen Ursachen ansieht und dabei mit der wahrscheinlichsten beginnt. Anhand des Stack-Traces lassen sich häufig nicht relevante Ursachen herausfiltern.
MTE im asynchronen Modus
Im asynchronen („async“) Modus von MTE stürzt SIGSEGV mit dem Code 8 (SEGV_MTEAERR) ab.
SEGV_MTEAERR-Fehler treten nicht sofort auf, wenn ein Programm einen ungültigen Arbeitsspeicherzugriff ausführt. Das Problem wird kurz nach dem Ereignis erkannt und das Programm wird an dieser Stelle beendet. Dieser Punkt ist in der Regel der nächste Systemaufruf, kann aber auch ein Timerunterbrechung sein – kurz gesagt, jede Umwandlung vom Nutzerbereich zum Kernel.
Bei SEGV_MTEAERR-Fehlern wird die Speicheradresse nicht beibehalten (sie wird immer als „-------“ angezeigt). Der Backtrace entspricht dem Moment, in dem die Bedingung erkannt wurde (d.h. beim nächsten Systemaufruf oder bei einem anderen Kontextwechsel), und nicht dem Zeitpunkt, zu dem der ungültige Zugriff ausgeführt wurde.
Das bedeutet, dass der „Haupt-Backtrace“ bei einem asynchronen MTE-Absturz in der Regel nicht relevant ist. Fehler im Async-Modus sind daher viel schwieriger zu beheben als Fehler im Sync-Modus. Sie zeigen am besten, dass im Code in der Nähe des Threads ein Speicherfehler vorliegt. Die Protokolle am Ende der Tombstone-Datei können einen Hinweis darauf geben, was tatsächlich passiert ist. Andernfalls empfehlen wir, den Fehler im Synchronisierungsmodus zu reproduzieren und die besseren Diagnosetools zu verwenden, die dieser Modus bietet.
Erweiterte Themen
Im Hintergrund wird jeder Heap-Zuweisung ein zufälliger 4‑Bit-Tag-Wert (0..15) zugewiesen. Dieser Wert wird in einer speziellen Metadatenregion gespeichert, die dem zugewiesenen Heap-Speicher entspricht. Derselbe Wert wird dem höchstwertigen Byte des Heap-Pointers zugewiesen, der von Funktionen wie malloc() oder operator new() zurückgegeben wird.
Wenn die Tag-Prüfung im Prozess aktiviert ist, vergleicht die CPU bei jedem Speicherzugriff automatisch das oberste Byte des Zeigers mit dem Speicher-Tag. Wenn die Tags nicht übereinstimmen, meldet die CPU einen Fehler, der zu einem Absturz führt.
Aufgrund der begrenzten Anzahl möglicher Tag-Werte ist dieser Ansatz probabilistisch. Jeder Speicherort, auf den nicht mit einem bestimmten Zeiger zugegriffen werden sollte, z. B. außerhalb des Gültigkeitsbereichs oder nach der Deaktivierung („hängender Zeiger“), hat wahrscheinlich einen anderen Tag-Wert und führt zu einem Absturz. Es besteht eine Wahrscheinlichkeit von etwa 7 %, dass kein einziges Vorkommen eines Bugs erkannt wird. Da die Tag-Werte zufällig zugewiesen werden, besteht eine unabhängige Wahrscheinlichkeit von etwa 93 %, dass der Fehler beim nächsten Mal erkannt wird.
Die Tag-Werte sind im Feld „Fehleradresse“ sowie im Registerdump zu sehen, wie unten hervorgehoben. In diesem Abschnitt können Sie prüfen, ob die Tags korrekt festgelegt sind, und sich andere Speicherzuweisungen in der Nähe mit demselben Tagwert ansehen. Diese können neben den im Bericht aufgeführten Ursachen ebenfalls zu dem Fehler führen. Wir gehen davon aus, dass dies vor allem für die Personen nützlich ist, die an der Implementierung von MTE selbst oder anderen Low-Level-Systemkomponenten arbeiten, und weniger für Entwickler.
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0800007ae92853a0 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
Im Absturzbericht wird außerdem ein spezieller Bereich „Speicher-Tags“ angezeigt, in dem Speicher-Tags um die Fehleradresse herum zu sehen sind. Im folgenden Beispiel stimmt das Zeiger-Tag „4“ nicht mit dem Speicher-Tag „a“ überein.
Memory tags around the fault address (0x0400007b43063db5), one tag per 16 bytes: 0x7b43063500: 0 f 0 2 0 f 0 a 0 7 0 8 0 7 0 e 0x7b43063600: 0 9 0 8 0 5 0 e 0 f 0 c 0 f 0 4 0x7b43063700: 0 b 0 c 0 b 0 2 0 1 0 4 0 7 0 8 0x7b43063800: 0 b 0 c 0 3 0 a 0 3 0 6 0 b 0 a 0x7b43063900: 0 3 0 4 0 f 0 c 0 3 0 e 0 0 0 c 0x7b43063a00: 0 3 0 2 0 1 0 8 0 9 0 4 0 3 0 4 0x7b43063b00: 0 5 0 2 0 5 0 a 0 d 0 6 0 d 0 2 0x7b43063c00: 0 3 0 e 0 f 0 a 0 0 0 0 0 0 0 4 =>0x7b43063d00: 0 0 0 a 0 0 0 e 0 d 0 [a] 0 f 0 e 0x7b43063e00: 0 7 0 c 0 9 0 a 0 d 0 2 0 0 0 c 0x7b43063f00: 0 0 0 6 0 b 0 8 0 3 0 0 0 5 0 e 0x7b43064000: 0 d 0 2 0 7 0 a 0 7 0 a 0 d 0 8 0x7b43064100: 0 b 0 2 0 b 0 4 0 1 0 6 0 d 0 4 0x7b43064200: 0 1 0 6 0 f 0 2 0 f 0 6 0 5 0 c 0x7b43064300: 0 1 0 4 0 d 0 6 0 f 0 e 0 1 0 8 0x7b43064400: 0 f 0 4 0 3 0 2 0 1 0 2 0 5 0 6
In Abschnitten eines Tombstones, die den Arbeitsspeicherinhalt um alle Registerwerte herum anzeigen, werden auch die Tag-Werte angezeigt.
memory near x10 ([anon:scudo:primary]): 0000007b4304a000 7e82000000008101 000003e9ce8b53a0 .......~.S...... 0700007b4304a010 0000200000006001 0000000000000000 .`... .......... 0000007b4304a020 7c03000000010101 000003e97c61071e .......|..a|.... 0200007b4304a030 0c00007b4304a270 0000007ddc4fedf8 p..C{.....O.}... 0000007b4304a040 84e6000000008101 000003e906f7a9da ................ 0300007b4304a050 ffffffff00000042 0000000000000000 B............... 0000007b4304a060 8667000000010101 000003e9ea858f9e ......g......... 0400007b4304a070 0000000100000001 0000000200000002 ................ 0000007b4304a080 f5f8000000010101 000003e98a13108b ................ 0300007b4304a090 0000007dd327c420 0600007b4304a2b0 .'.}......C{... 0000007b4304a0a0 88ca000000010101 000003e93e5e5ac5 .........Z^>.... 0a00007b4304a0b0 0000007dcc4bc500 0300007b7304cb10 ..K.}......s{... 0000007b4304a0c0 0f9c000000010101 000003e9e1602280 ........."`..... 0900007b4304a0d0 0000007dd327c780 0700007b7304e2d0 ..'.}......s{... 0000007b4304a0e0 0d1d000000008101 000003e906083603 .........6...... 0a00007b4304a0f0 0000007dd327c3b8 0000000000000000 ..'.}...........