Microdroïde

Microdroid est un système d'exploitation mini-Android qui s'exécute dans une pVM. Vous n'êtes pas obligé d'utiliser Microdroid, vous pouvez démarrer une VM avec n'importe quel système d'exploitation. Cependant, les principaux cas d'utilisation des pVM ne consistent pas à exécuter un système d'exploitation autonome, mais plutôt à offrir un environnement d'exécution isolé pour exécuter une partie d'une application avec des garanties de confidentialité et d'intégrité plus fortes que celles qu'Android peut offrir.

Avec les systèmes d'exploitation traditionnels, assurer une confidentialité et une intégrité élevées nécessite une quantité de travail considérable (souvent dupliquée) car les systèmes d'exploitation traditionnels ne correspondent pas à l'architecture globale d'Android. Par exemple, avec l'architecture Android standard, les développeurs doivent implémenter un moyen de charger et d'exécuter en toute sécurité une partie de leur application dans la pVM, et la charge utile est construite sur la glibc. L'application Android utilise Bionic, la communication nécessite un protocole personnalisé via vsock et le débogage à l'aide d'adb est un défi.

Microdroid comble ces lacunes en fournissant une image de système d'exploitation prête à l'emploi conçue pour nécessiter le moins d'efforts possible de la part des développeurs pour décharger une partie de leur application dans une pVM. Le code natif est construit sur Bionic, la communication s'effectue via Binder, permet d'importer des APEX depuis Android et expose un sous-ensemble de l'API Android, tel qu'un magasin de clés pour les opérations cryptographiques avec des clés matérielles. Dans l’ensemble, les développeurs devraient trouver dans Microdroid un environnement familier avec les outils auxquels ils se sont habitués dans le système d’exploitation Android complet.

Caractéristiques

Microdroid est une version allégée d'Android avec quelques composants supplémentaires spécifiques aux pVM. Microdroïde prend en charge :

  • Un sous-ensemble d'API NDK (toutes les API pour l'implémentation Android de libc et Bionic sont fournies)
  • Fonctionnalités de débogage, telles que adb, logcat, tombstone et gdb
  • Démarrage vérifié et SELinux activés
  • Chargement et exécution d'un binaire, ainsi que de bibliothèques partagées, intégrés dans un APK
  • Binder RPC sur vsock et échange de fichiers avec contrôles d'intégrité implicites
  • Chargement des APEX

Microdroid ne prend pas en charge :

  • API Java Android dans les packages android.\*

  • SystemServer et Zygote

  • Graphiques/interface utilisateur

  • HAL

Architecture microdroïde

Microdroid est similaire à Cuttlefish dans la mesure où les deux ont une architecture similaire à celle d'Android standard. Microdroid se compose des images de partition suivantes regroupées dans une image disque composite :

  • bootloader - Vérifie et démarre le noyau.
  • boot.img - Contient le noyau et le disque virtuel d'initialisation.
  • vendor_boot.img - Contient des modules de noyau spécifiques à la VM, tels que virtio.
  • super.img - Se compose de partitions logiques du système et du fournisseur.
  • vbmeta.img - Contient les métadonnées de démarrage vérifiées.

Les images de partition sont livrées dans Virtualization APEX et sont regroupées dans une image disque composite par VirtualizationService . En plus de l'image disque composite principale du système d'exploitation, VirtualizationService est responsable de la création de ces autres partitions :

  • payload - Un ensemble de partitions soutenues par les APEX et APK d'Android
  • instance - Une partition chiffrée pour conserver les données de démarrage vérifiées par instance, telles que le sel par instance, les clés publiques APEX de confiance et les compteurs d'annulation

Séquence d'amorçage

La séquence de démarrage du Microdroid se produit après le démarrage du périphérique . Le démarrage du périphérique est abordé dans le document Architecture . La figure 1 montre les étapes qui ont lieu pendant la séquence de démarrage du Microdroid :

Flux de démarrage sécurisé de l'instance de microdroïde

Figure 1. Flux de démarrage sécurisé de l'instance de microdroïde

Voici une explication des étapes :

  1. Le chargeur de démarrage est chargé en mémoire par crosvm et pvmfw commence à s'exécuter. Avant de passer au chargeur de démarrage, pvmfw effectue deux tâches :

    • Vérifie le chargeur de démarrage pour vérifier s'il provient d'une source fiable (Google ou un OEM).
    • Garantit que le même chargeur de démarrage est utilisé de manière cohérente lors de plusieurs démarrages de la même pVM grâce à l'utilisation de l'image d'instance. Plus précisément, la pVM est initialement démarrée avec une image d'instance vide. pvmfw stocke l'identité du chargeur de démarrage dans l'image de l'instance et la chiffre. Ainsi, la prochaine fois que la pVM sera démarrée avec la même image d'instance, pvmfw déchiffrera l'identité enregistrée à partir de l'image d'instance et vérifiera qu'il s'agit de la même que celle enregistrée précédemment. Si les identités diffèrent, pvmfw refuse de démarrer.

    Le chargeur de démarrage démarre ensuite Microdroid.

  2. Le chargeur de démarrage accède au disque d'instance. Semblable à pvmfw, le chargeur de démarrage dispose d'un lecteur de disque d'instance contenant des informations sur les images de partition utilisées dans cette instance lors des démarrages précédents, y compris la clé publique.

  3. Le chargeur de démarrage vérifie vbmeta et les partitions chaînées, telles que boot et super , et, en cas de succès, dérive les secrets pVM de l'étape suivante. Ensuite, Microdroid confie le contrôle au noyau.

  4. Étant donné que la super partition a déjà été vérifiée par le chargeur de démarrage (étape 3), le noyau monte la super partition sans condition. Comme pour Android complet, la super partition se compose de plusieurs partitions logiques montées sur dm-verity. Le contrôle est ensuite transmis au processus init , qui démarre divers services natifs. Le script init.rc est similaire à celui d'Android complet mais adapté aux besoins de Microdroid.

  5. Le processus init démarre le gestionnaire Microdroid, qui accède à l'image de l'instance. Le service de gestion Microdroid déchiffre l'image à l'aide de la clé transmise à l'étape précédente et lit les clés publiques et les compteurs d'annulation des APK et APEX clients auxquels cette pVM fait confiance. Ces informations sont utilisées ultérieurement par zipfuse et apexd lorsqu'ils montent respectivement l'APK client et les APEX demandés.

  6. Le service de gestion Microdroid démarre apexd .

  7. apexd monte les APEX dans les répertoires /apex/<name> . La seule différence entre la façon dont Android et Microdroid montent les APEX est que dans Microdroid, les fichiers APEX proviennent de périphériques de bloc virtuels ( /dev/vdc1 , …), et non de fichiers normaux ( /system/apex/*.apex ).

  8. zipfuse est le système de fichiers FUSE de Microdroid. zipfuse monte l'APK client, qui est essentiellement un fichier Zip en tant que système de fichiers. En dessous, le fichier APK est transmis en tant que périphérique de bloc virtuel par le pVM avec dm-verity, comme APEX. L'APK contient un fichier de configuration avec une liste d'APEX que le développeur de l'application a demandé pour cette instance pVM. La liste est utilisée par apexd lors de l'activation des APEX.

  9. Le flux de démarrage revient au service de gestion Microdroid. Le service de gestion communique ensuite avec le VirtualizationService d'Android à l'aide de Binder RPC afin qu'il puisse signaler des événements importants comme un crash ou un arrêt, et accepter des demandes telles que la fin de la pVM. Le service de gestion lit l'emplacement du binaire principal dans le fichier de configuration de l'APK et l'exécute.

Échange de fichiers (AuthFS)

Il est courant que les composants Android utilisent des fichiers pour l'entrée, la sortie et l'état et les transmettent en tant que descripteurs de fichiers (type ParcelFileDescriptor dans AIDL) avec un accès contrôlé par le noyau Android. AuthFS facilite des fonctionnalités similaires pour l'échange de fichiers entre des points de terminaison se méfiant mutuellement au-delà des limites pVM.

Fondamentalement, AuthFS est un système de fichiers distant avec des contrôles d'intégrité transparents sur les opérations d'accès individuelles, similaires à fs-verity . Les vérifications permettent au frontend, tel qu'un programme de lecture de fichiers exécuté dans une pVM, de détecter si le backend non fiable, généralement Android, a falsifié le contenu du fichier.

Pour échanger des fichiers, le backend ( fd\_server ) est démarré avec une configuration par fichier spécifiant s'il est destiné à l'entrée (lecture seule) ou à la sortie (lecture-écriture). Pour la saisie, l'interface impose que le contenu corresponde à un hachage connu, au-dessus d'une arborescence Merkle pour la vérification à l'accès. Pour la sortie, AuthFS maintient en interne une arborescence de hachage du contenu tel qu'observé lors des opérations d'écriture et peut renforcer l'intégrité lorsque les données sont relues.

Le transport sous-jacent est actuellement basé sur Binder RPC, mais cela pourrait changer à l'avenir pour optimiser les performances.

Gestion des clés

Les pVM sont fournies avec une clé de scellement stable adaptée aux données persistantes protégées et une clé d'attestation adaptée à la production de signatures produites de manière vérifiable par la pVM.

Classeur RPC

La majorité des interfaces d'Android sont exprimées en AIDL , qui repose sur le pilote du noyau Binder Linux. Pour prendre en charge les interfaces entre pVM, le protocole Binder a été réécrit pour fonctionner sur des sockets, vsock dans le cas des pVM. Le fonctionnement via des sockets permet d'utiliser les interfaces AIDL existantes d'Android dans ce nouvel environnement.

Pour configurer la connexion, un point de terminaison, tel que la charge utile pVM, crée un objet RpcServer , enregistre un objet racine et commence à écouter les nouvelles connexions. Les clients peuvent se connecter à ce serveur à l'aide d'un objet RpcSession , obtenir l'objet Binder et l'utiliser exactement comme un objet Binder est utilisé avec le pilote Binder du noyau.