vendredi, décembre 1, 2023

Blog de sécurité en ligne de Google : Rust sans système d’exploitation sous Android


L’année dernière, nous avons écrit sur la façon dont le déplacement du code natif sous Android de C++ vers Rust a entraîné moins de vulnérabilités de sécurité. La plupart des composants que nous avons mentionnés à l’époque étaient des services système dans l’espace utilisateur (fonctionnant sous Linux), mais ce ne sont pas les seuls composants généralement écrits dans des langages à mémoire dangereuse. De nombreux composants critiques pour la sécurité d’un système Android s’exécutent dans un environnement « bare metal », en dehors du noyau Linux, et sont historiquement écrits en C. Dans le cadre de nos efforts pour renforcer le firmware sur les appareils Androidnous utilisons également de plus en plus Rust dans ces environnements nus.

A cette fin, nous avons réécrit le micrologiciel de la VM protégée (pVM) du framework de virtualisation Android dans Rust pour fournir une base sécurisée en mémoire pour la racine de confiance pVM. Ce micrologiciel remplit une fonction similaire à celle d’un chargeur de démarrage et a été initialement construit sur U-Boot, un chargeur de démarrage open source largement utilisé. Cependant, U-Boot n’a pas été conçu dans un souci de sécurité dans un environnement hostile, et il y a eu de nombreuses failles de sécurité trouvé en raison d’un accès mémoire hors limites, d’un dépassement d’entier et d’une corruption de la mémoire. Ses pilotes VirtIO en particulier présentaient un certain nombre de manquant ou problématique vérifications des limites. Nous avons résolu les problèmes spécifiques que nous avons trouvés dans U-Boot, mais en tirant parti de Rust, nous pouvons éviter ce type de vulnérabilités de sécurité de la mémoire à l’avenir. Le nouveau firmware Rust pVM est sorti sur Android 14.

Dans le cadre de cet effort, nous avons contribué à la communauté Rust en utilisant et en contribuant aux caisses existantes lorsque cela était possible, et en publiant également un certain nombre de nouvelles caisses. Par exemple, pour VirtIO dans le firmware pVM, nous avons passé du temps à corriger les bugs et les problèmes de solidité dans le firmware existant. pilotes virtio crate, ainsi que l’ajout de nouvelles fonctionnalités, et aident désormais à maintenir cette caisse. Nous avons publié des caisses pour passer des appels PSCI et autres appels Arm SMCCCet pour gestion des tables de pages. Ce n’est qu’un début ; nous prévoyons de publier davantage de caisses Rust pour prendre en charge la programmation nue sur une gamme de plates-formes. Ces caisses sont également utilisées en dehors d’Android, comme dans Projet Chêne et le section métallique nue de nôtre Rouille complète cours.

Ingénieurs de formation

De nombreux ingénieurs ont été positivement surpris par la productivité et le plaisir de travailler avec Rust, offrant des fonctionnalités de haut niveau intéressantes, même dans des environnements de bas niveau. Les ingénieurs travaillant sur ces projets viennent d’horizons divers. Notre cours complet sur Rust a aidé les programmeurs expérimentés et novices à se mettre rapidement à niveau. De manière anecdotique, le système de types Rust (y compris le vérificateur d’emprunt et les durées de vie) permet d’éviter de commettre des erreurs faciles à commettre en C ou C++, telles que des fuites de pointeurs vers des valeurs allouées à la pile hors de portée.

L’un des participants à notre cours Bare Metal Rust a déclaré ceci :

"types can be built that bring in all of Rust's niceties and safeties and 
yet still compile down to extremely efficient code like writes
of constants to memory-mapped IO."

97 % des participants ayant répondu à une enquête ont convenu que le cours valait la peine d’y consacrer du temps.

Avantages et défis

Les pilotes de périphériques sont souvent écrits de manière orientée objet pour plus de flexibilité, même en C. Les traits Rust, qui peuvent être considérés comme une forme de polymorphisme au moment de la compilation, fournissent une abstraction de haut niveau utile pour cela. Dans de nombreux cas, ce problème peut être entièrement résolu au moment de la compilation, sans surcharge d’exécution liée à la répartition dynamique via des tables virtuelles ou des structures de pointeurs de fonction.

Il y a eu quelques défis. Le système de types de Safe Rust est conçu avec l’hypothèse implicite que la seule mémoire dont le programme doit se soucier est allouée par le programme (que ce soit sur la pile, sur le tas ou de manière statique) et utilisée uniquement par le programme. Les programmes nus doivent souvent gérer MMIO et la mémoire partagée, ce qui brise cette hypothèse. Cela nécessite généralement beaucoup de code dangereux et de pointeurs bruts, avec des outils d’encapsulation limités. Il existe un certain désaccord au sein de la communauté Rust sur la solidité des références à l’espace MMIO, et les fonctionnalités permettant de travailler avec des pointeurs bruts dans Rust stable sont actuellement quelque peu limitées. La stabilisation de offset_of, slice_ptr_get, slice_ptr_len, et d’autres fonctionnalités nocturnes amélioreront cela, mais il reste difficile d’encapsuler proprement. Une meilleure syntaxe pour accéder aux champs de structure et aux index de tableau via des pointeurs bruts sans créer de références serait également utile.

La concurrence introduite par les gestionnaires d’interruptions et d’exceptions peut également être gênante, car ils ont souvent besoin d’accéder à un état mutable partagé mais ne peuvent pas compter sur la possibilité de prendre des verrous. De meilleures abstractions pour les sections critiques seront quelque peu utiles, mais il existe certaines exceptions qui ne peuvent pratiquement pas être désactivées, telles que les défauts de page utilisés pour implémenter la copie sur écriture ou d’autres stratégies de mappage de pages à la demande.

Un autre problème que nous avons rencontré est que certaines opérations dangereuses, telles que la manipulation de la table des pages, ne peuvent pas être encapsulées proprement car elles ont des implications en matière de sécurité pour l’ensemble du programme. Habituellement, dans Rust, nous sommes capables d’encapsuler les opérations non sécurisées (opérations qui peuvent provoquer un comportement indéfini dans certaines circonstances, car elles ont des contrats que le compilateur ne peut pas vérifier) ​​dans des wrappers sécurisés où nous garantissons les conditions préalables nécessaires afin que cela ne soit possible pour aucun l’appelant pour provoquer un comportement indéfini. Cependant, mapper ou démapper des pages dans une partie du programme peut rendre d’autres parties du programme invalides, nous n’avons donc pas trouvé de moyen de fournir une interface sûre et totalement générale pour cela. Il convient de noter que les mêmes préoccupations s’appliquent à un programme écrit en C, où le programmeur doit toujours raisonner sur la sécurité de l’ensemble du programme.

Certaines personnes qui adoptent Rust pour des cas d’utilisation sans système d’exploitation ont fait part de leurs inquiétudes concernant la taille des binaires. Nous l’avons vu dans certains cas ; par exemple, notre binaire du micrologiciel Rust pVM fait environ 460 Ko, contre 220 Ko pour la version C précédente. Cependant, il ne s’agit pas d’une comparaison équitable car nous avons également ajouté davantage de fonctionnalités qui nous ont permis de supprimer d’autres composants de la chaîne de démarrage, de sorte que la taille globale de tous les composants de la chaîne de démarrage des VM était comparable. Nous n’avons pas non plus particulièrement optimisé la taille binaire dans ce cas ; la rapidité et l’exactitude étaient plus importantes. Dans les cas où la taille binaire est critique, la compilation avec optimisation de la taillefaire attention aux dépendances et éviter la machinerie de formatage de chaînes de Rust dans les versions de version permet généralement des résultats comparables à ceux du C.

Le support architectural est une autre préoccupation. La rouille est généralement Bien soutenu sur les cœurs Arm et RISC-V que l’on voit le plus souvent, mais la prise en charge d’architectures plus ésotériques (par exemple, le DSP Qualcomm Hexagon inclus dans de nombreux SoC Qualcomm utilisés dans les téléphones Android) peut faire défaut par rapport à C.

L’avenir de Rust sans système d’exploitation

Dans l’ensemble, malgré ces défis et limitations, nous avons toujours trouvé que Rust constituait une amélioration significative par rapport au C (ou C++), à la fois en termes de sécurité et de productivité, dans tous les cas d’utilisation sans système d’exploitation où nous l’avons essayé jusqu’à présent. . Nous prévoyons de l’utiliser autant que possible.

En plus du travail sur le framework de virtualisation Android, l’équipe travaillant sur Sûr (l’environnement d’exécution de confiance open source utilisé sur les téléphones Pixel, entre autres) a travaillé dur pour ajouter la prise en charge des applications de confiance écrites en Rust. Par exemple, le référence à l’implémentation de l’application de confiance KeyMint est maintenant dans Rust. Et il y a bien plus à venir sur les futurs appareils Android, alors que nous continuons à utiliser Rust pour améliorer la sécurité des appareils auxquels vous faites confiance.

Related Articles

LAISSER UN COMMENTAIRE

S'il vous plaît entrez votre commentaire!
S'il vous plaît entrez votre nom ici

Latest Articles