Comprensione dei report MTE

Gli arresti anomali di SIGSEGV con codice 9 (SEGV_MTESERR) o codice 8 (SEGV_MTEAERR) sono errori di tagging della memoria. Memory Tagging Extension (MTE) è una funzionalità Armv9 supportata in Android 12 e versioni successive. MTE è un'implementazione hardware della memoria contrassegnata. Fornisce una protezione granulare della memoria per il rilevamento e la mitigazione dei bug di sicurezza della memoria .

In C/C++, un puntatore restituito da una chiamata a malloc() o all'operatore new() o funzioni simili può essere utilizzato solo per accedere alla memoria entro i limiti di tale allocazione e solo mentre l'allocazione è attiva (non liberata o cancellato-ndr). MTE viene utilizzato in Android per rilevare violazioni di questa regola, indicate nei rapporti sugli arresti anomali come problemi "Buffer Overflow"/"Buffer Underflow" e "Use After Free".

MTE ha due modalità: sincrona (o "sync") e asincrona (o "async"). Il primo funziona più lentamente ma fornisce una diagnostica più accurata. Quest'ultimo funziona più velocemente, ma può fornire solo dettagli approssimativi. Tratteremo entrambi separatamente, poiché la diagnostica è leggermente diversa.

Modalità sincrona MTE

Nella modalità sincrona ("sync") di MTE, SIGSEGV si blocca con il codice 9 (SEGV_MTESERR).

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)

Tutti i report sugli arresti anomali di MTE contengono il consueto dump del registro e backtrace per il punto in cui è stato rilevato il problema. La riga "Cause:" per un errore rilevato da MTE conterrà "[MTE]" come nell'esempio precedente, insieme a maggiori dettagli. In questo caso, il tipo specifico di errore rilevato era "Use after free" e "0 byte in un'allocazione di 32 byte a 0x7ae92853a0" ci dice la dimensione e l'indirizzo dell'allocazione e l'offset nell'allocazione che abbiamo provato ad accedere.

I rapporti sugli arresti anomali dell'MTE includono anche backtrace aggiuntivi, non solo quello dal punto di rilevamento.

Gli errori "Utilizza dopo libero" aggiungono le sezioni "deallocata da" e "allocata da" al dump dell'arresto anomalo, mostrando le tracce dello stack nel momento in cui questa memoria è stata deallocata (prima che fosse utilizzata!) e l'ora in cui è stata precedentemente allocata. Questi ti dicono anche quale thread ha effettuato l'allocazione/deallocazione. Tutti e tre i thread di rilevamento, di allocazione e di deallocazione sono uguali in questo semplice esempio, ma nei casi reali più complessi ciò non è necessariamente vero e sapere che differiscono può essere un indizio importante per trovare una concorrenza -bug correlato.

Gli errori "Buffer Overflow" e "Buffer Underflow" forniscono solo un'ulteriore traccia dello stack "allocato da", poiché per definizione non sono stati ancora deallocati (o verrebbero visualizzati come "Use After Free"):

Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0
[...]
backtrace:
[...]
allocated by thread 13949:

Nota l'uso della parola "giusto" qui: questo significa che ti stiamo dicendo quanti byte dopo la fine dell'allocazione c'era l'accesso errato; un underflow direbbe "sinistra" e sarebbe un numero di byte prima dell'inizio dell'allocazione.

Molteplici cause potenziali

A volte i report SEGV_MTESERR contengono la seguente riga:

Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.

Ciò accade quando esistono diversi buoni candidati per l'origine dell'errore e non è possibile stabilire quale sia la causa effettiva. Stampiamo fino a 3 di questi candidati in ordine approssimativo di probabilità e lasciamo l'analisi all'utente.

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...]

Nell'esempio precedente, abbiamo rilevato due allocazioni recenti allo stesso indirizzo di memoria che avrebbe potuto essere l'obiettivo previsto dell'accesso alla memoria non valido. Ciò può verificarsi quando le allocazioni riutilizzano la memoria libera, ad esempio se si dispone della sequenza nuovo, libero, nuovo, libero, nuovo, libero, accesso. L'allocazione più recente viene stampata per prima.

Euristiche dettagliate per la determinazione della causa

La "Causa" di un arresto anomalo dovrebbe mostrare l'allocazione di memoria da cui è stato originariamente derivato il puntatore a cui si accede. Sfortunatamente, l'hardware MTE non ha modo di tradurre un puntatore con un tag non corrispondente in un'allocazione. Per spiegare un arresto anomalo di SEGV_MTESERR, Android analizza i seguenti dati:

  • L'indirizzo di errore (incluso il tag puntatore).
  • Un elenco delle allocazioni heap recenti con analisi dello stack e tag di memoria.
  • Allocazioni attuali (live) vicine e relativi tag di memoria.

Qualsiasi memoria deallocata di recente all'indirizzo di errore in cui il tag di memoria corrisponde al tag dell'indirizzo di errore è una potenziale causa "Use After Free".

Qualsiasi memoria attiva nelle vicinanze in cui il tag di memoria corrisponde al tag dell'indirizzo di errore è una potenziale causa di "Buffer Overflow" (o "Buffer Underflow").

Le allocazioni più vicine alla colpa, sia nel tempo che nello spazio, sono considerate più probabili di quelle lontane.

Poiché la memoria deallocata viene spesso riutilizzata e il numero di valori di tag diversi è piccolo (meno di 16), non è raro trovare diversi probabili candidati e non esiste alcun modo per trovare automaticamente la vera causa. Questo è il motivo per cui a volte i rapporti MTE elencano più cause potenziali.

Si consiglia allo sviluppatore dell'app di esaminare le potenziali cause iniziando da quella più probabile. Spesso è semplice filtrare le cause non correlate in base all'analisi dello stack.

Modalità asincrona MTE

Nella modalità asincrona ("asincrona") di MTE, SIGSEGV si blocca con il codice 8 (SEGV_MTEAERR).

Gli errori SEGV_MTEAERR non si verificano immediatamente quando un programma esegue un accesso alla memoria non valido. Il problema viene rilevato poco dopo l'evento e a quel punto il programma viene invece terminato. Questo punto è tipicamente la successiva chiamata di sistema, ma può anche essere un'interruzione del timer - in breve, qualsiasi transizione dallo spazio utente al kernel.

Gli errori SEGV_MTEAERR non preservano l'indirizzo di memoria (è sempre mostrato come "-------"). Il backtrace corrisponde al momento in cui la condizione è stata rilevata (cioè alla successiva chiamata di sistema o altro cambio di contesto), e non quando è stato eseguito l'accesso non valido.

Ciò significa che il backtrace "principale" in un crash MTE asincrono di solito non è rilevante . Gli errori della modalità asincrona sono quindi molto più difficili da eseguire il debug rispetto agli errori della modalità di sincronizzazione. È meglio interpretarli come se mostrassero l'esistenza di un bug di memoria nel codice vicino nel thread specificato. I registri nella parte inferiore del file di rimozione definitiva possono fornire un suggerimento su ciò che è realmente accaduto. In caso contrario, l'azione consigliata è riprodurre l'errore in modalità di sincronizzazione e utilizzare la migliore diagnostica fornita dalla modalità di sincronizzazione!

Argomenti avanzati

Dietro le quinte, il tagging della memoria funziona assegnando un valore tag casuale a 4 bit (0..15) a ogni allocazione dell'heap. Questo valore viene archiviato in un'area di metadati speciale che corrisponde alla memoria heap allocata. Lo stesso valore viene assegnato al byte più significativo del puntatore all'heap restituito da funzioni come malloc() o l'operatore new().

Se nel processo è attivato il controllo delle variabili, ad ogni accesso alla memoria la CPU confronta automaticamente il byte superiore del puntatore con la variabile di memoria. Se i tag non corrispondono, la CPU segnala un errore che porta ad un crash.

A causa del numero limitato di possibili valori dei tag, questo approccio è probabilistico. Qualsiasi posizione di memoria a cui non è possibile accedere con un determinato puntatore, ad esempio fuori limite o dopo la deallocazione ("puntatore penzolante"), è probabile che abbia un valore di tag diverso e causi un arresto anomalo. Esiste una probabilità del 7% circa di non rilevare alcuna singola occorrenza di un bug. Poiché i valori dei tag vengono assegnati in modo casuale, esiste una probabilità indipendente pari a circa il 93% di rilevare il bug la prossima volta che si verifica.

I valori dei tag possono essere visualizzati nel campo dell'indirizzo di errore nonché nel dump del registro, come evidenziato di seguito. Questa sezione può essere utilizzata per verificare che i tag siano impostati in modo corretto, nonché per vedere altre allocazioni di memoria vicine con lo stesso valore di tag in quanto possono essere potenziali cause dell'errore oltre a quelle elencate nel rapporto. Ci aspettiamo che questo sia utile principalmente per le persone che lavorano sull'implementazione dello stesso MTE o di altri componenti di sistema di basso livello, piuttosto che per gli sviluppatori.

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

Nel rapporto sugli arresti anomali viene visualizzata anche una sezione speciale "Tag di memoria" che mostra i tag di memoria attorno all'indirizzo di errore. Nell'esempio seguente il tag puntatore "4" non corrisponde al tag di memoria "a".

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

Le sezioni di una rimozione definitiva che mostrano il contenuto della memoria attorno a tutti i valori del registro mostrano anche i valori dei tag.

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