MTE-Berichte verstehen

SIGSEGV-Abstürze mit Code 9 (SEGV_MTESERR) oder Code 8 (SEGV_MTEAERR) sind Speicher-Tagging-Fehler. Memory Tagging Extension (MTE) ist eine Armv9-Funktion, die in Android 12 und höher unterstützt wird. MTE ist eine Hardware-Implementierung von Tagged Memory. Es bietet feinkörnigen Speicherschutz zur Erkennung und Behebung von Speichersicherheitsfehlern .

In C/C++ kann ein Zeiger, der von einem Aufruf von malloc() oder Operator new() oder ähnlichen Funktionen zurückgegeben wird, nur für den Zugriff auf den Speicher innerhalb der Grenzen dieser Zuweisung verwendet werden, und nur, solange die Zuweisung aktiv ist (nicht freigegeben oder). gelöscht). 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“-Probleme bezeichnet werden.

MTE verfügt über zwei Modi: synchron (oder „sync“) und asynchron (oder „async“). Ersteres läuft langsamer, bietet aber eine genauere Diagnose. Letzterer läuft schneller, kann aber nur ungefähre Angaben machen. Wir werden beide getrennt behandeln, da die Diagnose leicht unterschiedlich ist.

Synchronmodus MTE

Im synchronen („sync“) Modus von MTE stürzt SIGSEGV mit 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 Register-Dump und die Rückverfolgung für den Punkt, an dem das Problem erkannt wurde. Die Zeile „Ursache:“ für einen von MTE erkannten Fehler enthält „[MTE]“ wie im obigen Beispiel, zusammen mit weiteren Details. In diesem Fall handelte es sich bei der spezifischen Art des erkannten Fehlers um „Use after free“, und „0 Bytes in eine 32-Byte-Zuweisung bei 0x7ae92853a0“ verrät uns die Größe und Adresse der Zuweisung sowie den Offset in der Zuweisung, den wir vorgenommen haben habe versucht, darauf zuzugreifen.

MTE-Absturzberichte enthalten auch zusätzliche Rückverfolgungen, nicht nur die vom Ort der Erkennung.

„Use After Free“-Fehler fügen dem Absturzspeicherauszug die Abschnitte „deallocated by“ und „allocated by“ hinzu und zeigen die Stack-Traces zum Zeitpunkt der Freigabe dieses Speichers (bevor er verwendet wurde!) sowie den Zeitpunkt, zu dem er zuvor zugewiesen wurde. Diese sagen Ihnen auch, welcher Thread die Zuweisung/Freigabe vorgenommen hat. In diesem einfachen Beispiel sind alle drei Erkennungsthreads, Zuweisungsthreads und Freigabethreads gleich, aber in komplexeren realen Fällen trifft dies nicht unbedingt zu, und das Wissen, dass sie sich unterscheiden, kann ein wichtiger Hinweis beim Finden einer Parallelität sein -bezogener Fehler.

Die Fehler „Pufferüberlauf“ und „Pufferunterlauf“ bieten nur eine zusätzliche Stapelspur „Zugewiesen von“, da die Zuweisung per Definition noch nicht aufgehoben wurde (sonst würden sie als „Verwenden nach Freigabe“ 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 „richtig“ hier: Dies bedeutet, dass wir Ihnen mitteilen, wie viele Bytes nach dem Ende der Zuweisung der falsche Zugriff erfolgte; Ein Unterlauf würde „links“ lauten und eine Anzahl von Bytes vor dem Beginn der Zuweisung liegen.

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.

Dies geschieht, wenn es mehrere gute Kandidaten für die Fehlerursache gibt und wir nicht sagen können, was die tatsächliche Ursache ist. Wir drucken bis zu drei solcher Kandidaten in der ungefähren Reihenfolge ihrer Wahrscheinlichkeit aus und überlassen die Analyse dem Benutzer.

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 haben wir zwei aktuelle Zuweisungen an derselben Speicheradresse entdeckt, die das beabsichtigte Ziel des ungültigen Speicherzugriffs gewesen sein könnten. Dies kann passieren, wenn Zuweisungen freien Speicher wiederverwenden – beispielsweise wenn Sie die Reihenfolge „Neu, Frei, Neu, Frei, Neu, Frei, Zugriff“ haben. Die neuere Zuordnung wird zuerst gedruckt.

Detaillierte Heuristik zur Ursachenbestimmung

Die „Ursache“ eines Absturzes sollte die Speicherzuordnung anzeigen, von der der Zugriffszeiger ursprünglich abgeleitet wurde. Leider verfügt die MTE-Hardware nicht über die Möglichkeit, einen Zeiger mit einem nicht übereinstimmenden Tag in eine Zuordnung zu übersetzen. Um einen SEGV_MTESERR-Absturz zu erklären, analysiert Android die folgenden Daten:

  • Die Fehleradresse (einschließlich des Zeiger-Tags).
  • Eine Liste der letzten Heap-Zuweisungen mit Stack-Traces und Speicher-Tags.
  • In der Nähe befindliche aktuelle (Live-)Zuweisungen und ihre Speichertags.

Jeder kürzlich freigegebene Speicher an der Fehleradresse, bei dem das Speicher-Tag mit dem Fehleradressen-Tag übereinstimmt, ist eine potenzielle Ursache für „Use After Free“.

Jeder in der Nähe befindliche Live-Speicher, bei dem das Speicher-Tag mit dem Fehleradress-Tag übereinstimmt, ist eine mögliche Ursache für einen „Pufferüberlauf“ (oder „Pufferunterlauf“).

Zuordnungen, die zeitlich oder räumlich näher an der Störung liegen, gelten als wahrscheinlicher als solche, die weiter entfernt liegen.

Da freigegebener Speicher häufig wiederverwendet wird und die Anzahl der verschiedenen Tag-Werte gering ist (weniger als 16), ist es nicht ungewöhnlich, mehrere wahrscheinliche Kandidaten zu finden, und es gibt keine Möglichkeit, die wahre Ursache automatisch zu finden. Aus diesem Grund werden in MTE-Berichten manchmal mehrere mögliche Ursachen aufgeführt.

Es wird empfohlen, dass der App-Entwickler mögliche Ursachen untersucht, beginnend mit der wahrscheinlichsten. Es ist oft einfach, unabhängige Ursachen anhand des Stack-Trace herauszufiltern.

Asynchroner Modus MTE

Im asynchronen („async“) Modus von MTE stürzt SIGSEGV mit Code 8 (SEGV_MTEAERR) ab.

SEGV_MTEAERR-Fehler treten nicht sofort auf, wenn ein Programm einen ungültigen Speicherzugriff durchführt. Das Problem wird kurz nach dem Ereignis erkannt und das Programm stattdessen zu diesem Zeitpunkt beendet. Dieser Punkt ist normalerweise der nächste Systemaufruf, kann aber auch ein Timer-Interrupt sein – kurz gesagt, jeder Übergang vom Userspace zum Kernel.

SEGV_MTEAERR-Fehler bewahren die Speicheradresse nicht (sie wird immer als „-------“ angezeigt). Der Backtrace entspricht dem Moment, in dem die Bedingung erkannt wurde (d. h. beim nächsten Systemaufruf oder einem anderen Kontextwechsel), und nicht dem Zeitpunkt, an dem der ungültige Zugriff durchgeführt wurde.

Dies bedeutet, dass der „Haupt“-Backtrace bei einem asynchronen MTE-Absturz normalerweise nicht relevant ist. Fehler im Asynchronmodus sind daher viel schwieriger zu debuggen als Fehler im Synchronmodus. Sie lassen sich am besten so verstehen, dass sie das Vorhandensein eines Speicherfehlers im nahegelegenen Code im jeweiligen Thread anzeigen. Protokolle am Ende der Tombstone-Datei können einen Hinweis darauf geben, was tatsächlich passiert ist. Andernfalls besteht die empfohlene Vorgehensweise darin, den Fehler im Synchronisierungsmodus zu reproduzieren und die bessere Diagnose zu verwenden, die der Synchronisierungsmodus bietet!

Fortgeschrittene Themen

Unter der Haube funktioniert das Speicher-Tagging, indem jeder Heap-Zuweisung ein zufälliger 4-Bit-Tag-Wert (0..15) zugewiesen wird. Dieser Wert wird in einem speziellen Metadatenbereich gespeichert, der dem zugewiesenen Heap-Speicher entspricht. Derselbe Wert wird dem höchstwertigen Byte des Heap-Zeigers zugewiesen, der von Funktionen wie malloc() oder Operator new() zurückgegeben wird.

Wenn dabei die Tag-Prüfung aktiviert ist, vergleicht die CPU bei jedem Speicherzugriff automatisch das oberste Byte des Zeigers mit dem Speichertag. Stimmen die Tags nicht überein, meldet die CPU einen Fehler, der zum Absturz führt.

Aufgrund der begrenzten Anzahl möglicher Tag-Werte ist dieser Ansatz probabilistisch. Jeder Speicherort, auf den mit einem bestimmten Zeiger nicht zugegriffen werden sollte – beispielsweise außerhalb der Grenzen oder nach der Freigabe („baumelnder Zeiger“) – hat wahrscheinlich einen anderen Tag-Wert und verursacht einen Absturz. Es besteht eine Wahrscheinlichkeit von ca. 7 %, dass kein einziges Auftreten eines Fehlers erkannt wird. Da die Tag-Werte zufällig zugewiesen werden, besteht eine unabhängige Chance von ca. 93 %, den Fehler beim nächsten Auftreten zu erkennen.

Die Tag-Werte sind im Fehleradressfeld sowie im Register-Dump zu sehen, wie unten hervorgehoben. Dieser Abschnitt kann verwendet werden, um zu überprüfen, ob die Tags richtig eingestellt sind, und um andere Speicherzuordnungen in der Nähe mit demselben Tag-Wert zu sehen, da diese über die im Bericht aufgeführten hinaus potenzielle Fehlerursachen sein können. Wir gehen davon aus, dass dies vor allem für die Leute von Nutzen sein wird, die an der Implementierung von MTE selbst oder anderen Low-Level-Systemkomponenten arbeiten, und nicht 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 erscheint auch ein spezieller Abschnitt „Speichertags“, der Speichertags rund um die Fehleradresse anzeigt. Im folgenden Beispiel stimmte 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

Abschnitte eines Tombstones, die Speicherinhalte rund um alle Registerwerte anzeigen, zeigen auch deren Tag-Werte an.

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