Les plantages SIGSEGV avec le code 9 (SEGV_MTESERR) ou le code 8 (SEGV_MTEAERR) sont des erreurs de taggage de mémoire. La Memory Tagging Extension (MTE) est une fonctionnalité Armv9 compatible avec Android 12 et versions ultérieures. MTE est une implémentation matérielle de la mémoire taguée. Il offre une protection de la mémoire précise pour la détection et l'atténuation des bugs de sécurité de la mémoire.
En C/C++, un pointeur renvoyé à partir d'un appel de malloc() ou de l'opérateur new() ou de fonctions similaires ne peut être utilisé que pour accéder à la mémoire dans les limites de cette allocation, et uniquement tant que l'allocation est active (pas de libération ni de suppression). MTE est utilisé dans Android pour détecter les cas de non-respect de cette règle, qui sont désignés dans les rapports d'erreur comme "Dépassement de mémoire tampon"/"Dépassement de mémoire tampon sous-jacent" et "Utilisation après libération".
MTE propose deux modes: synchrone (ou "sync") et asynchrone (ou "async"). La première s'exécute plus lentement, mais fournit des diagnostics plus précis. Ce dernier s'exécute plus rapidement, mais ne peut fournir que des détails approximatifs. Nous allons les aborder séparément, car les diagnostics sont légèrement différents.
MTE en mode synchrone
En mode synchrone ("sync") de MTE, SIGSEGV plante avec le code 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)
Tous les rapports d'erreur MTE contiennent le vidage de registre et la rétrotrace habituels pour le point où le problème a été détecté. La ligne "Cause:" d'une erreur détectée par MTE contient "[MTE]", comme dans l'exemple ci-dessus, ainsi que des informations plus détaillées. Dans ce cas, le type d'erreur spécifique détecté était "Utilisation après libération", et "0 octets dans une allocation de 32 octets à 0x7ae92853a0" nous indique la taille et l'adresse de l'allocation, ainsi que le décalage dans l'allocation à laquelle nous avons essayé d'accéder.
Les rapports d'erreur MTE incluent également des backtraces supplémentaires, et pas seulement celui du point de détection.
Les erreurs "Use After Free" ajoutent les sections "deallocated by" (Libéré par) et "allocated by" (Alloué par) au dump de plantage, qui affichent les traces de la pile au moment où cette mémoire a été libérée (avant qu'elle ne soit utilisée) et au moment où elle a été allouée précédemment. Ils vous indiquent également quel thread a effectué l'allocation/la désallocation. Dans cet exemple simple, les trois threads de détection, d'allocation et de désallocation sont identiques, mais dans des cas plus complexes, ce n'est pas nécessairement vrai. Savoir qu'ils diffèrent peut être un indice important pour trouver un bug lié à la concurrence.
Les erreurs "Buffer Overflow" et "Buffer Underflow" ne fournissent qu'une piste de pile "allocated by" supplémentaire, car par définition, elles n'ont pas encore été désalloquées (ou elles s'afficheraient comme "Use After Free"):
Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0 [...] backtrace: [...] allocated by thread 13949:
Notez l'utilisation du mot "droit" ici: cela signifie que nous vous indiquons le nombre d'octets au-delà de la fin de l'allocation pour l'accès incorrect. Un sous-dépassement indiquerait "gauche" et serait un nombre d'octets avant le début de l'allocation.
Plusieurs causes possibles
Parfois, les rapports SEGV_MTESERR contiennent la ligne suivante:
Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.
Cela se produit lorsqu'il existe plusieurs candidats plausibles à l'origine de l'erreur et que nous ne pouvons pas déterminer la cause réelle. Nous imprimons jusqu'à trois candidats de ce type dans l'ordre approximatif de probabilité, et laissons l'analyse à l'utilisateur.
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...]
Dans l'exemple ci-dessus, nous avons détecté deux allocations récentes à la même adresse mémoire qui auraient pu être la cible visée de l'accès à la mémoire incorrect. Cela peut se produire lorsque les allocations réutilisent de la mémoire libre, par exemple si vous avez la séquence suivante : nouveau, libre, nouveau, libre, nouveau, libre, accès. L'allocation la plus récente est imprimée en premier.
Heuristiques de détermination de la cause détaillée
La "Cause" d'un plantage doit indiquer l'allocation de mémoire à partir de laquelle le pointeur a été dérivé à l'origine. Malheureusement, le matériel MTE n'a aucun moyen de traduire un pointeur avec une balise non correspondante en allocation. Pour expliquer un plantage SEGV_MTESERR, Android analyse les données suivantes:
- Adresse de l'erreur (y compris la balise du pointeur).
- Liste des allocations de tas récentes avec des traces de pile et des tags de mémoire.
- Allocations actuelles (en direct) à proximité et leurs tags de mémoire
Toute mémoire récemment désallouée à l'adresse de défaillance où la balise de mémoire correspond à la balise de l'adresse de défaillance est une cause potentielle d'erreur "Use After Free".
Toute mémoire active à proximité dont la balise de mémoire correspond à la balise d'adresse de défaut peut être à l'origine d'un "dépassement de tampon" (ou "sous-dépassement de tampon").
Les allocations les plus proches de la défaillance (dans le temps ou dans l'espace) sont considérées comme plus probables que celles qui sont éloignées.
Étant donné que la mémoire désallouée est souvent réutilisée et que le nombre de valeurs de balise différentes est faible (moins de 16), il n'est pas rare de trouver plusieurs candidats probables, et il n'existe aucun moyen de trouver automatiquement la véritable cause. C'est pourquoi les rapports MTE peuvent parfois lister plusieurs causes potentielles.
Nous recommandons au développeur de l'application d'examiner les causes potentielles en commençant par la plus probable. Il est souvent facile de filtrer les causes non liées en fonction de la trace de la pile.
MTE en mode asynchrone
En mode asynchrone ("async") de MTE, SIGSEGV plante avec le code 8 (SEGV_MTEAERR).
Les erreurs SEGV_MTEAERR ne se produisent pas immédiatement lorsqu'un programme effectue un accès à la mémoire non valide. Le problème est détecté peu de temps après l'événement, et le programme est alors arrêté. Ce point correspond généralement au prochain appel système, mais il peut également s'agir d'une interruption de minuteur. En bref, il peut s'agir de toute transition de l'espace utilisateur vers le noyau.
Les erreurs SEGV_MTEAERR ne conservent pas l'adresse mémoire (elle est toujours affichée sous la forme "-------"). La rétrotrace correspond au moment où la condition a été détectée (c'est-à-dire au prochain appel système ou à un autre changement de contexte), et non au moment où l'accès incorrect a été effectué.
Cela signifie que la rétrotrace "principale" dans un plantage MTE asynchrone n'est généralement pas pertinente. Les défaillances en mode asynchrone sont donc beaucoup plus difficiles à déboguer que les défaillances en mode synchrone. Il est préférable de les considérer comme indiquant l'existence d'un bug de mémoire dans le code à proximité du thread donné. Les journaux en bas du fichier de tombstone peuvent vous donner une idée de ce qui s'est passé. Sinon, nous vous recommandons de reproduire l'erreur en mode synchronisation et d'utiliser les meilleurs diagnostics qu'il fournit.
Rubriques avancées
Sous le capot, le taggage de mémoire fonctionne en attribuant une valeur de tag aléatoire de 4 bits (0 à 15) à chaque allocation de tas. Cette valeur est stockée dans une région de métadonnées spéciale qui correspond à la mémoire de tas allouée. La même valeur est attribuée à l'octet le plus significatif du pointeur de tas renvoyé par des fonctions telles que malloc() ou l'opérateur new().
Lorsque la vérification des tags est activée dans le processus, le processeur compare automatiquement l'octet supérieur du pointeur avec la balise de mémoire pour chaque accès à la mémoire. Si les tags ne correspondent pas, le processeur signale une erreur qui entraîne un plantage.
En raison du nombre limité de valeurs de balise possibles, cette approche est probabiliste. Tout emplacement de mémoire auquel il ne faut pas accéder avec un pointeur donné (par exemple, en dehors des limites ou après une désallocation (pointeur dangling)) a probablement une valeur de balise différente et peut provoquer un plantage. Il y a environ 7% de chances de ne détecter aucune occurrence d'un bug. Étant donné que les valeurs des balises sont attribuées de manière aléatoire, il y a environ 93% de chances de détecter le bug la prochaine fois qu'il se produira.
Les valeurs de balise sont visibles dans le champ d'adresse de défaut, ainsi que dans le dump du registre, comme indiqué ci-dessous. Cette section permet de vérifier que les balises sont définies de manière appropriée, ainsi que d'afficher d'autres allocations de mémoire à proximité avec la même valeur de balise, car elles peuvent être des causes potentielles de l'erreur en plus de celles listées dans le rapport. Cette fonctionnalité devrait être principalement utile aux personnes qui travaillent sur l'implémentation de MTE elle-même ou d'autres composants système de bas niveau, plutôt qu'aux développeurs.
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
Une section spéciale "Tags de mémoire" s'affiche également dans le rapport d'erreur, qui affiche les tags de mémoire autour de l'adresse de défaut. Dans l'exemple ci-dessous, la balise de pointeur "4" ne correspond pas à la balise de mémoire "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
Les sections d'une pierre tombale qui affichent le contenu de la mémoire autour de toutes les valeurs de registre affichent également leurs valeurs de balise.
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 ..'.}...........