Extension MTE (Memory Tagging Extension) Arm

Arm v9 introduit l'extension d'ajout de balises de mémoire (MTE) Arm, une implémentation matérielle de la mémoire taguée.

De manière générale, MTE associe des métadonnées supplémentaires à chaque allocation/désallocation de mémoire. Il attribue un tag à un emplacement de mémoire, qui peut ensuite être associé à des pointeurs qui font référence à cet emplacement de mémoire. Lors de l'exécution, le processeur vérifie que le pointeur et les tags de métadonnées correspondent à chaque chargement et enregistrement.

Dans Android 12, l'allocateur de mémoire de tas du noyau et de l'espace utilisateur peut augmenter chaque allocation avec des métadonnées. Cela permet de détecter les bugs d'utilisation après libération et de dépassement de mémoire tampon, qui sont la source la plus courante de bugs de sécurité au niveau de la mémoire dans nos bases de code.

Modes de fonctionnement avec MTE

MTE comporte trois modes de fonctionnement :

  • Mode synchrone (SYNC)
  • Mode asynchrone (ASYNC)
  • Mode asymétrique (ASYMM)

Mode synchrone (SYNC)

Ce mode est optimisé pour la détection correcte des bugs plutôt que pour les performances. Il peut être utilisé comme un outil précis de détection de bugs quand un impact plus important sur les performances est acceptable. Lorsque ce mode est activé avec MTE, il agit également comme une mesure de sécurité. En cas de non-concordance des tags, le processeur interrompt immédiatement l'exécution et met fin au processus avec SIGSEGV (code SEGV_MTESERR) et des informations complètes sur l'accès à la mémoire et l'adresse défaillante.

Nous vous recommandons d'utiliser ce mode lors des tests comme alternative à HWASan/KASAN ou en production lorsque le processus cible représente une surface d'attaque vulnérable. En outre, lorsque le mode ASYNC a indiqué la présence d'un bug, vous pouvez obtenir un rapport de bug précis à l'aide des API d'exécution pour passer l'exécution en mode SYNC.

Lorsqu'il s'exécute en mode SYNC, l'allocateur Android enregistre les traces de pile pour toutes les allocations et désallocations, et les utilise pour fournir de meilleurs rapports d'erreur qui incluent une explication d'une erreur de mémoire, telle que l'utilisation après libération ou le dépassement de mémoire tampon, ainsi que les traces de pile des événements de mémoire concernés. Ces rapports fournissent des informations avec davantage de contexte, et permettent de suivre et de corriger les bugs plus facilement.

Mode asynchrone (ASYNC)

Ce mode est optimisé pour les performances plutôt que pour la précision des rapports de bugs. Il peut également être utilisé pour détecter les bugs de sécurité de la mémoire à faible coût.
En cas de non-concordance des tags, le processeur poursuit l'exécution jusqu'à l'entrée de noyau la plus proche (par exemple, un appel système ou une interruption de minuteur), où il met fin au processus avec SIGSEGV (code SEGV_MTEAERR) sans enregistrer l'adresse défaillante ni l'accès à la mémoire.
Nous vous recommandons d'utiliser ce mode en production sur des codebases bien testés, où la densité de bugs de sécurité liés à la mémoire est connue pour être faible (ce qui est obtenu en utilisant le mode SYNC pendant les tests).

Mode asymétrique (ASYMM)

Le mode MTE asymétrique, une fonctionnalité supplémentaire d'Arm v8.7-A, permet une vérification synchrone des lectures de mémoire et une vérification asynchrone des écritures de mémoire, avec des performances semblables à celles du mode ASYNC. Dans la plupart des cas, ce mode est une amélioration par rapport au mode ASYNC. Nous vous recommandons de l'utiliser à la place du mode ASYNC chaque fois qu'il est disponible.

Pour cette raison, aucune des API décrites ci-dessous ne mentionne le mode asymétrique. Au lieu de cela, l'OS peut être configuré pour toujours utiliser le mode asymétrique lorsque le mode asynchrone est demandé. Pour en savoir plus, veuillez consulter la section "Configurer le niveau MTE préféré spécifique au processeur".

MTE dans l'espace utilisateur

Les sections suivantes décrivent comment activer MTE pour les processus système et les applications. MTE est désactivé par défaut, sauf si l'une des options ci-dessous est définie pour un processus particulier (voir les composants pour lesquels MTE est activé ci-dessous).

Activer MTE à l'aide du système de compilation

En tant que propriété à l'échelle du processus, MTE est contrôlé par le paramètre de compilation de l'exécutable principal. Les options suivantes permettent de modifier ce paramètre pour des exécutables individuels ou pour des sous-répertoires entiers de l'arborescence source. Le paramètre est ignoré pour les bibliothèques ou toute cible qui n'est ni exécutable ni un test.

1. Activer MTE dans Android.bp (exemple) pour un projet spécifique :

Mode MTE Paramètre
MTE asynchrone
  sanitize: {
  memtag_heap: true,
  }
MTE synchrone
  sanitize: {
  memtag_heap: true,
  diag: {
  memtag_heap: true,
  },
  }

ou dans Android.mk:

Mode MTE Paramètre
Asynchronous MTE LOCAL_SANITIZE := memtag_heap
Synchronous MTE LOCAL_SANITIZE := memtag_heap
LOCAL_SANITIZE_DIAG := memtag_heap

2. Activer MTE sur un sous-répertoire de l'arborescence source à l'aide d'une variable produit :

Mode MTE Liste d'inclusion Liste d'exclusion
async PRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS MEMTAG_HEAP_ASYNC_INCLUDE_PATHS PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
Synchronisation PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS MEMTAG_HEAP_SYNC_INCLUDE_PATHS

ou

Mode MTE Paramètre
MTE asynchrone MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
MTE synchrone MEMTAG_HEAP_SYNC_INCLUDE_PATHS

ou en spécifiant le chemin d'exclusion d'un exécutable :

Mode MTE Paramètre
MTE asynchrone PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS MEMTAG_HEAP_EXCLUDE_PATHS
MTE synchrone

Exemple (utilisation semblable à PRODUCT_CFI_INCLUDE_PATHS)

  PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHS=vendor/$(vendor)
  PRODUCT_MEMTAG_HEAP_EXCLUDE_PATHS=vendor/$(vendor)/projectA \
                                    vendor/$(vendor)/projectB

Activer MTE à l'aide des propriétés système

Les paramètres de compilation ci-dessus peuvent être remplacés au moment de l'exécution en définissant la propriété système suivante :

arm64.memtag.process.<basename> = (off|sync|async)

basename correspond au nom de base de l'exécutable.

Par exemple, pour définir /system/bin/ping ou /data/local/tmp/ping sur l'utilisation de MTE asynchrone, utilisez adb shell setprop arm64.memtag.process.ping async.

Activer MTE à l'aide d'une variable d'environnement

Une autre façon de remplacer le paramètre de compilation pour les processus natifs (non-applications) consiste à définir la variable d'environnement : MEMTAG_OPTIONS=(off|sync|async). Si la variable d'environnement et la propriété système sont définies, la variable est prioritaire.

Activer MTE pour les applications

Si aucune valeur n'est spécifiée, MTE est désactivé par défaut. Toutefois, les applications qui souhaitent utiliser MTE peuvent le faire en définissant android:memtagMode sous la balise <application> ou <process> dans le fichier AndroidManifest.xml.

android:memtagMode=(off|default|sync|async)

Lorsqu'il est défini sur la balise <application>, l'attribut affecte tous les processus utilisés par l'application et peut être ignoré pour chaque processus en spécifiant la balise <process>.

Pour effectuer des tests, vous pouvez utiliser les modifications de compatibilité afin de définir la valeur par défaut de l'attribut memtagMode pour une application où aucune valeur n'est spécifiée dans le fichier manifeste (ou dont la valeur est default).
Vous trouverez ces modifications sous System > Advanced > Developer options > App Compatibility Changes dans le menu des paramètres généraux. Si vous définissez NATIVE_MEMTAG_ASYNC ou NATIVE_MEMTAG_SYNC, MTE est activé pour une application particulière.
Vous pouvez également définir cela à l'aide de la commande am comme suit :

$ adb shell am compat enable NATIVE_MEMTAG_[A]SYNC my.app.name

Créer une image système MTE

Nous vous recommandons vivement d'activer MTE sur tous les binaires natifs lors du développement et de la mise en service. Cela permet de détecter les bugs de sécurité de la mémoire de manière précoce et offre une couverture utilisateur réaliste, si elle est activée dans les versions de test.

Nous vous recommandons vivement d'activer MTE en mode synchrone sur tous les binaires natifs pendant le développement.

SANITIZE_TARGET=memtag_heap SANITIZE_TARGET_DIAG=memtag_heap m

Comme pour toute variable du système de compilation, SANITIZE_TARGET peut être utilisé comme variable d'environnement ou comme paramètre make (par exemple, dans un fichier product.mk).
Notez que cela active MTE pour tous les processus natifs, mais pas pour les applications (qui sont issues de zygote64) pour lesquelles MTE peut être activé en suivant les instructions ci-dessus.

Configurer le niveau MTE préféré spécifique au processeur

Sur certains processeurs, les performances de MTE en mode ASYMM ou même SYNC peuvent être similaires à celles du mode ASYNC. Il est donc intéressant d'activer des vérifications plus strictes sur ces processeurs lorsqu'un mode de vérification moins strict est demandé, afin de bénéficier des avantages de la détection des erreurs des vérifications plus strictes sans les inconvénients en termes de performances.
Par défaut, les processus configurés pour s'exécuter en mode ASYNC s'exécutent en mode ASYNC sur tous les processeurs. Pour configurer le noyau afin qu'il exécute ces processus en mode SYNC sur des processeurs spécifiques, la valeur "sync" doit être écrite dans l'entrée sysfs /sys/devices/system/cpu/cpu<N>/mte_tcf_preferred au moment du démarrage. Pour ce faire, utilisez un script d'initialisation. Par exemple, pour configurer les processeurs 0 à 1 afin qu'ils exécutent les processus en mode ASYNCHRONOUS en mode SYNCHRONOUS, et les processeurs 2 à 3 afin qu'ils s'exécutent en mode ASYMMETRICAL, les éléments suivants peuvent être ajoutés à la clause init d'un script d'initialisation du fournisseur :

  write /sys/devices/system/cpu/cpu0/mte_tcf_preferred sync
  write /sys/devices/system/cpu/cpu1/mte_tcf_preferred sync
  write /sys/devices/system/cpu/cpu2/mte_tcf_preferred asymm
  write /sys/devices/system/cpu/cpu3/mte_tcf_preferred asymm

Les fichiers Tombstone des processus en mode ASYNC exécutés en mode SYNC contiendront une trace de pile précise de l'emplacement de l'erreur de mémoire. Toutefois, elles n'incluront pas de trace de pile d'allocation ou de désallocation. Ces traces de pile ne sont disponibles que si le processus est configuré pour s'exécuter en mode SYNC.

int mallopt(M_THREAD_DISABLE_MEM_INIT, level)

level est égal à 0 ou 1.
Désactive l'initialisation de la mémoire dans malloc et évite de modifier les tags de mémoire sauf si cela est nécessaire pour la correction.

int mallopt(M_MEMTAG_TUNING, level)

level correspond à :

  • M_MEMTAG_TUNING_BUFFER_OVERFLOW
  • M_MEMTAG_TUNING_UAF

Sélectionne la stratégie d'attribution des tags.

  • Le paramètre par défaut est M_MEMTAG_TUNING_BUFFER_OVERFLOW.
  • M_MEMTAG_TUNING_BUFFER_OVERFLOW : permet la détection déterministe des bugs de dépassement et de dépassement négatif de tampon linéaire en attribuant des valeurs de tag distinctes aux allocations adjacentes. Ce mode a une probabilité légèrement réduite de détecter les bugs d'utilisation après libération, car seule la moitié des valeurs de tag possibles sont disponibles pour chaque emplacement de mémoire. Veuillez noter que MTE ne peut pas détecter les débordements dans le même granule de tag (bloc aligné sur 16 octets) et peut manquer de petits débordements même dans ce mode. Un tel débordement ne peut pas être la cause d'une corruption de la mémoire, car la mémoire d'un granule n'est jamais utilisée pour plusieurs allocations.
  • M_MEMTAG_TUNING_UAF : active les tags aléatoires indépendants pour une probabilité uniforme d'environ 93 % de détecter les bugs spatiaux (dépassement de mémoire tampon) et temporels (utilisation après libération).

En plus des API décrites ci-dessus, les utilisateurs expérimentés peuvent souhaiter connaître les informations suivantes :

  • La définition du registre matérielPSTATE.TCO peut temporairement supprimer la vérification des balises (exemple). Par exemple, lorsque vous copiez une plage de mémoire dont le contenu de la balise est inconnu ou lorsque vous résolvez un goulot d'étranglement des performances dans une boucle chaude.
  • Lorsque vous utilisez M_HEAP_TAGGING_LEVEL_SYNC, le gestionnaire d'erreur système fournit des informations supplémentaires, telles que des traces de pile d'allocation et de désallocation. Cette fonctionnalité nécessite d'accéder aux bits de balise. Pour l'activer, transmettez l'indicateur SA_EXPOSE_TAGBITS lors de la définition du gestionnaire de signaux. Il est recommandé à tout programme qui définit son propre gestionnaire de signaux et délègue les plantages inconnus à celui du système de faire de même.

MTE dans le noyau

Pour activer KASAN accéléré par MTE pour le noyau, configurez le noyau avec CONFIG_KASAN=y, CONFIG_KASAN_HW_TAGS=y. Ces configurations sont activées par défaut sur les noyaux GKI, à partir de Android 12-5.10.
Cela peut être contrôlé au moment du démarrage à l'aide des arguments de ligne de commande suivants :

  • kasan=[on|off] : active ou désactive KASAN (par défaut : on)
  • kasan.mode=[sync|async] : choisissez entre le mode synchrone et asynchrone (par défaut : sync).
  • kasan.stacktrace=[on|off] : indique si les traces de pile doivent être collectées (par défaut : on).
    • La collecte des traces de pile nécessite également stack_depot_disable=off.
  • kasan.fault=[report|panic] : indique si le rapport doit uniquement être imprimé ou si le noyau doit également paniquer (par défaut : report). Quelle que soit cette option, la vérification des tags est désactivée après la première erreur signalée.

Nous vous recommandons vivement d'utiliser le mode SYNC lors de la mise en service, du développement et des tests. Cette option doit être activée globalement pour tous les processus à l'aide de la variable d'environnement ou du système de compilation. Dans ce mode, les bugs sont détectés tôt dans le processus de développement, la base de code est stabilisée plus rapidement et le coût de la détection des bugs plus tard en production est évité.

Nous vous recommandons vivement d'utiliser le mode ASYNC en production. Il s'agit d'un outil à faible coût permettant de détecter la présence de bugs de sécurité de la mémoire dans un processus, ainsi que d'une défense en profondeur supplémentaire. Une fois un bug détecté, le développeur peut utiliser les API d'exécution pour passer en mode SYNC et obtenir une trace de pile précise à partir d'un échantillon d'utilisateurs.

Nous vous recommandons vivement de configurer le niveau MTE préféré spécifique au processeur pour le SoC. Le mode Asymm présente généralement les mêmes caractéristiques de performances que le mode ASYNC et est presque toujours préférable. Les petits cœurs dans l'ordre affichent souvent des performances similaires dans les trois modes et peuvent être configurés pour privilégier SYNC.

Les développeurs doivent vérifier la présence de plantages en consultant /data/tombstones, logcat ou en surveillant le pipeline DropboxManager du fournisseur pour les bugs des utilisateurs finaux. Pour en savoir plus sur le débogage du code natif Android, consultez cette page.

Composants de plate-forme compatibles avec MTE

Dans Android 12, un certain nombre de composants système essentiels à la sécurité utilisent MTE ASYNC pour détecter les plantages des utilisateurs finaux et servir de couche supplémentaire de défense en profondeur. Ces composants sont les suivants :

  • Utilitaires et daemons réseau (à l'exception de netd)
  • Bluetooth, SecureElement, HAL NFC et applications système
  • Daemon statsd
  • system_server
  • zygote64 (pour permettre aux applications d'utiliser MTE)

Ces cibles ont été sélectionnées en fonction des critères suivants :

  • Un processus privilégié (défini comme un processus ayant accès à quelque chose que le domaine SELinux unprivileged_app n'a pas)
  • Traite des entrées non fiables (règle des deux)
  • Ralentissement acceptable des performances (le ralentissement ne crée pas de latence visible par l'utilisateur)

Nous encourageons les fournisseurs à activer MTE en production pour davantage de composants, en respectant les critères mentionnés ci-dessus. Pendant le développement, nous vous recommandons de tester ces composants en mode SYNC pour détecter les bugs faciles à corriger et évaluer l'impact du mode ASYNC sur leurs performances.
À l'avenir, Android prévoit d'étendre la liste des composants système sur lesquels MTE est activé, en fonction des caractéristiques de performances des futures conceptions matérielles.