Microdroid

Microdroid ist ein Mini-Android-Betriebssystem, das in einer pVM ausgeführt wird. Sie müssen Microdroid nicht verwenden, sondern können eine VM mit einem beliebigen Betriebssystem starten. Die primären Anwendungsfälle für pVMs sind jedoch nicht die Ausführung eines eigenständigen Betriebssystems, sondern vielmehr das Angebot einer isolierten Ausführungsumgebung für die Ausführung eines Teils einer App mit stärkeren Vertraulichkeits- und Integritätsgarantien als Android.

Bei herkömmlichen Betriebssystemen erfordert die Bereitstellung von starker Vertraulichkeit und Integrität einen erheblichen Aufwand (oft doppelt), da herkömmliche Betriebssysteme nicht zur übergreifenden Android-Architektur passen. Bei der Standard-Android-Architektur müssen Entwickler beispielsweise eine Möglichkeit implementieren, einen Teil ihrer App sicher in die pVM zu laden und auszuführen. Die Nutzlast wird mit glibc erstellt. Die Android-App verwendet Bionic, die Kommunikation erfordert ein benutzerdefiniertes Protokoll über vsock und das Debugging mit adb ist schwierig.

Microdroid schließt diese Lücken, indem es ein sofort einsatzbereites Betriebssystem-Image bereitstellt, das den Aufwand für Entwickler minimiert, einen Teil ihrer App in eine pVM auszulagern. Der native Code wird mit Bionic erstellt, die Kommunikation erfolgt über Binder. Außerdem können APEX-Dateien vom Host-Android importiert werden und eine Teilmenge der Android API wird verfügbar gemacht, z. B. Keystore für kryptografische Vorgänge mit hardwaregestützten Schlüsseln. Insgesamt sollten Entwickler Microdroid als vertraute Umgebung mit den Tools empfinden, die sie vom vollständigen Android-Betriebssystem gewohnt sind.

Features

Microdroid ist eine abgespeckte Version von Android mit einigen zusätzlichen Komponenten, die speziell für pVMs entwickelt wurden. Microdroid unterstützt Folgendes:

  • Eine Teilmenge der NDK APIs (alle APIs für die Android-Implementierung von libc und Bionic sind verfügbar)
  • Debugging-Features wie adb, logcat, tombstone und gdb
  • Bootmodus mit Verifikation und SELinux
  • Laden und Ausführen einer Binärdatei zusammen mit freigegebenen Bibliotheken, die in ein APK eingebettet sind
  • Binder RPC über vsock und Austausch von Dateien mit impliziten Integritätsprüfungen
  • Laden von APEX-Dateien

Microdroid unterstützt Folgendes nicht:

  • Android Java APIs in den android.\*-Paketen

  • SystemServer und Zygote

  • Grafiken/Benutzeroberfläche

  • HALs

Microdroid-Architektur

Microdroid ähnelt Cuttlefish insofern, als beide eine Architektur haben, die der von Standard-Android ähnelt. Microdroid besteht aus den folgenden Partitions-Images, die in einem zusammengesetzten Laufwerk-Image zusammengefasst sind:

  • bootloader : Überprüft und startet den Kernel.
  • boot.img : Enthält den Kernel und die init-Ramdisk.
  • vendor_boot.img : Enthält VM-spezifische Kernel-Module wie virtio.
  • super.img : Besteht aus logischen System- und Anbieterpartitionen.
  • vbmeta.img : Enthält Metadaten für den verifizierten Bootmodus.

Die Partitions-Images werden im Virtualization APEX ausgeliefert und von VirtualizationService in einem zusammengesetzten Laufwerk-Image verpackt. Neben dem zusammengesetzten Laufwerk-Image des Hauptbetriebssystems ist VirtualizationService für die Erstellung dieser anderen Partitionen verantwortlich:

  • payload : Eine Reihe von Partitionen, die von den APEX-Dateien und APKs von Android unterstützt werden
  • instance : Eine verschlüsselte Partition zum Speichern von instanzspezifischen Daten für den verifizierten Bootmodus, z. B. instanzspezifisches Salt, öffentliche Schlüssel für vertrauenswürdige APEX-Dateien und Rollback Zähler

Bootsequenz

Die Microdroid-Bootsequenz erfolgt nach dem Geräte-Boot. Das Geräte-Boot wird im Abschnitt pVM-Firmware des Dokuments zur Architektur beschrieben. Abbildung 1 zeigt die Schritte, die während der Microdroid-Bootsequenz ausgeführt werden:

Sicherer Bootflow der Microdroid-Instanz

Abbildung 1. Sicherer Bootflow der Microdroid-Instanz

Hier eine Erklärung der Schritte:

  1. Der Bootloader wird von crosvm in den Arbeitsspeicher geladen und pvmfw wird ausgeführt. Bevor zu dem Bootloader gesprungen wird, führt pvmfw zwei Aufgaben aus:

    • Der Bootloader wird überprüft, um festzustellen, ob er aus einer vertrauenswürdigen Quelle stammt (Google oder ein OEM).
    • Durch die Verwendung des Instanz-Images wird sichergestellt, dass derselbe Bootloader bei mehreren Starts derselben pVM verwendet wird. Insbesondere wird die pVM zunächst mit einem leeren Instanz-Image gestartet. pvmfw speichert die Identität des Bootloaders im Instanz-Image und verschlüsselt sie. Wenn die pVM das nächste Mal mit demselben Instanz-Image gestartet wird, entschlüsselt pvmfw die gespeicherte Identität aus dem Instanz-Image und prüft, ob sie mit der zuvor gespeicherten Identität übereinstimmt. Wenn sich die Identitäten unterscheiden, wird der Start von pvmfw abgelehnt.

    Der Bootloader startet dann Microdroid.

  2. Der Bootloader greift auf das Instanzlaufwerk zu. Ähnlich wie pvmfw hat der Bootloader ein Instanzlaufwerk mit Informationen zu den Partitions-Images, die bei früheren Starts in dieser Instanz verwendet wurden, einschließlich des öffentlichen Schlüssels.

  3. Der Bootloader überprüft vbmeta und die verketteten Partitionen wie boot und super und leitet bei Erfolg die pVM-Secrets der nächsten Stufe ab. Dann übergibt Microdroid die Steuerung an den Kernel.

  4. Da die Superpartition bereits vom Bootloader überprüft wurde (Schritt 3), stellt der Kernel die Superpartition bedingungslos bereit. Wie beim vollständigen Android besteht die Superpartition aus mehreren logischen Partitionen, die über dm-verity bereitgestellt werden. Die Steuerung wird dann an den init-Prozess übergeben, der verschiedene native Dienste startet. Das init.rc-Skript ähnelt dem des vollständigen Android, ist aber auf die Anforderungen von Microdroid zugeschnitten.

  5. Der init-Prozess startet den Microdroid-Manager, der auf das Instanz-Image zugreift. Der Microdroid-Managerdienst entschlüsselt das Image mit dem Schlüssel, der von der vorherigen Stufe übergeben wurde, und liest die öffentlichen Schlüssel und Rollback-Zähler der Client-APK und der APEX-Dateien, denen diese pVM vertraut. Diese Informationen werden später von zipfuse und apexd verwendet, wenn sie die Client-APK bzw. die angeforderten APEX-Dateien bereitstellen.

  6. Der Microdroid-Managerdienst startet apexd.

  7. apexd stellt die APEX-Dateien in den Verzeichnissen /apex/<name> bereit. Der einzige Unterschied zwischen der Bereitstellung von APEX-Dateien in Android und Microdroid besteht darin, dass die APEX-Dateien in Microdroid von virtuellen Blockgeräten (/dev/vdc1, …) und nicht von regulären Dateien (/system/apex/*.apex) stammen.

  8. zipfuse ist das FUSE-Dateisystem von Microdroid. zipfuse stellt die Client-APK bereit, die im Wesentlichen eine ZIP-Datei als Dateisystem ist. Die APK-Datei wird von der pVM mit dm-verity als virtuelles Blockgerät übergeben, genau wie APEX. Die APK enthält eine Konfigurationsdatei mit einer Liste von APEX-Dateien, die der App-Entwickler für diese pVM-Instanz angefordert hat. Die Liste wird von apexd beim Aktivieren von APEX-Dateien verwendet.

  9. Der Bootflow kehrt zum Microdroid-Managerdienst zurück. Der Managerdienst kommuniziert dann über Binder RPC mit VirtualizationService von Android, um wichtige Ereignisse wie Abstürze oder Herunterfahren zu melden und Anfragen wie das Beenden der pVM zu akzeptieren. Der Managerdienst liest den Speicherort der Hauptbinärdatei aus der Konfigurationsdatei der APK und führt sie aus.

Dateiaustausch (AuthFS)

Android-Komponenten verwenden häufig Dateien für Eingabe, Ausgabe und Status und übergeben sie als Dateideskriptoren (ParcelFileDescriptor-Typ in AIDL). Der Zugriff wird vom Android-Kernel gesteuert. AuthFS ermöglicht eine ähnliche Funktionalität für den Austausch von Dateien zwischen Endpunkten, die sich gegenseitig nicht vertrauen, über pVM-Grenzen hinweg.

AuthFS ist im Grunde ein Remote-Dateisystem mit transparenten Integritätsprüfungen für einzelne Zugriffsoperationen, ähnlich wie fs-verity. Mit den Prüfungen kann das Frontend, z. B. ein Programm zum Lesen von Dateien, das in einer pVM ausgeführt wird, erkennen, ob das nicht vertrauenswürdige Backend (in der Regel Android) den Dateiinhalt manipuliert hat.

Für den Austausch von Dateien wird das Backend (fd\_server) mit einer Konfiguration pro Datei gestartet, in der angegeben wird, ob es für die Eingabe (schreibgeschützt) oder Ausgabe (Lese-/Schreibzugriff) vorgesehen ist. Bei der Eingabe erzwingt das Frontend, dass die Inhalte mit einem bekannten Hash übereinstimmen, zusätzlich zu einem Merkle-Baum für die Überprüfung beim Zugriff. Bei der Ausgabe verwaltet AuthFS intern einen Hash-Baum der Inhalte, wie sie bei Schreibvorgängen beobachtet werden, und kann die Integrität erzwingen, wenn die Daten wieder gelesen werden.

Der zugrunde liegende Transport basiert derzeit auf Binder RPC. Dies kann sich jedoch in Zukunft ändern, um die Leistung zu optimieren.

Schlüsselverwaltung

pVMs werden mit einem stabilen Sealing-Schlüssel bereitgestellt, der sich zum Schutz persistenter Daten eignet, und einem Attestierungsschlüssel, der sich zum Erstellen von Signaturen eignet, die nachweislich von der pVM erstellt wurden.

Binder RPC

Die meisten Android-Schnittstellen werden in AIDL ausgedrückt, das auf dem Binder-Linux-Kernel-Treiber basiert. Zur Unterstützung von Schnittstellen zwischen pVMs wurde das Binder-Protokoll neu geschrieben, um über Sockets zu funktionieren, vsock im Fall von pVMs. Durch die Verwendung von Sockets können die vorhandenen AIDL-Schnittstellen von Android in dieser neuen Umgebung verwendet werden.

Um die Verbindung einzurichten, erstellt ein Endpunkt, z. B. die pVM-Nutzlast, ein RpcServer-Objekt, registriert ein Stammobjekt und wartet auf neue Verbindungen. Clients können mit einem RpcSession-Objekt eine Verbindung zu diesem Server herstellen, das Binder-Objekt abrufen und es genau so verwenden, wie ein Binder-Objekt mit dem Binder-Kernel-Treiber verwendet wird.