
Environnement avec un noyau vulnérable pour l'exploitation du pilote TEE (CVE-2021-44733)
Récemment, une vulnérabilité de type use-after-free a été découverte dans le sous-système TEE du noyau Linux, jusqu'à la version 5.15.11 incluse, et s'est vu attribuer le CVE-2021-44733 [1].
À première vue, elle ne semblait pas exploitable pour plusieurs raisons, mais après une analyse plus approfondie du chemin de code vulnérable et en implémentant un exploit de preuve de concept grossier, il a été possible d'écraser un pointeur de fonction dans le noyau. Aucune charge utile d'élévation de privilèges n'est présentée dans cet article, mais l'environnement complet pour exécuter OPTEE et l'exploit est disponible pour des tests supplémentaires, voir 'Mise en place de l'environnement'.
Un TEE (Trusted Execution Environment) est un OS de confiance s'exécutant dans un environnement sécurisé, par exemple TrustZone sur les CPU ARM. Un pilote TEE gère les détails nécessaires pour communiquer avec le TEE. Parmi les tâches les plus importantes du pilote, on trouve la fourniture d'une API générique vers le TEE basée sur la spécification Globalplatform TEE Client API [3], mais aussi la gestion de la mémoire partagée entre Linux et le TEE. Ce sous-système peut être activé en configurant CONFIG_OPTEE dans les configurations du noyau pour les architectures ARM.
Le monde sécurisé contient l'OS de confiance désigné OP-TEE OS [4]. Au-dessus de cet OS, il est possible d'avoir des applications dites Trusted Applications (TA) en cours d'exécution qui peuvent effectuer certaines opérations dans l'environnement isolé, voir Figure 1.
Figure 1: Vue d'ensemble du TEE - d'après la présentation de Linaro [5]
Le monde normal (espace utilisateur/noyau Linux) peut interagir avec ces applications en utilisant des applications clientes (CA) et l'API exposée par le sous-système TEE. Une CA peut ouvrir une session vers une TA spécifique et invoquer des fonctions implémentées par la TA. Le passage des arguments entre la TA et la CA se fait via la mémoire partagée. L'interaction entre une CA et une TA utilisant tous les appels système pertinents est décrite ci-dessous.
Une CA ouvre /dev/tee[0-9] pour communiquer avec le pilote. Notez que pour la méthode conventionnelle d'utilisation de ces API, cela se fait implicitement via libteec.
La mémoire partagée peut être enregistrée par la CA à l'aide de l'IOCTL TEE_IOC_SHM_ALLOC. Cela alloue de la mémoire partagée et retourne un descripteur de fichier que l'espace utilisateur peut utiliser dans le cadre de mmap.
L'étape suivante consiste à établir une session à l'aide de l'IOCTL TEE_IOC_OPEN_SESSION et en spécifiant l'uuid d'une TA spécifique. Cet uuid est codé en dur lors de la compilation de la TA.
Pour invoquer une fonction spécifique dans la TA, la CA l'invoque en spécifiant l'identifiant d'une fonction ainsi que les arguments d'entrée, cela se fait à l'aide de TEE_IOC_INVOKE.
Lorsque la CA a terminé toutes ses requêtes, la session peut être fermée à l'aide de TEE_IOC_CLOSE_SESSION.
Figure 2: Session entre CA et TA - d'après la présentation de Linaro [5]
Une grande partie de la communication entre les clients et le TEE est opaque pour le pilote. La tâche principale du pilote est de gérer le contexte, recevoir les requêtes des clients, les transmettre au TEE et renvoyer les résultats [2].