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 jedem Betriebssystem starten. Bei den Hauptanwendungsfällen für PVMs wird jedoch kein eigenständiges Betriebssystem ausgeführt, sondern eine isolierte Ausführungsumgebung, um einen Teil einer App mit stärkeren Vertraulichkeits- und Integritätsgarantien auszuführen, als Android bieten kann.

Bei herkömmlichen Betriebssystemen ist es mit einem erheblichen Arbeitsaufwand verbunden, für eine hohe Vertraulichkeit und Integrität zu sorgen, da sie nicht zur übergeordneten Android-Architektur passen. Bei der standardmäßigen Android-Architektur müssen Entwickler beispielsweise eine Möglichkeit implementieren, einen Teil ihrer App sicher in der pVM zu laden und auszuführen. Die Nutzlast wird dann mit glibc erstellt. Die Android-App verwendet Bionic, die Kommunikation erfordert ein benutzerdefiniertes Protokoll über vsock und das Debuggen mit adb ist schwierig.

Microdroid füllt diese Lücken, indem es ein vorgefertigtes Betriebssystem-Image bereitstellt, das so konzipiert ist, dass Entwickler mit dem geringsten Aufwand für die Auslagerung eines Teils ihrer App in eine pVM vorbereitet sind. Nativer Code wird für Bionic erstellt, die Kommunikation erfolgt über Binder. Außerdem können APEXes vom Android-Host importiert werden und es wird ein Teil der Android API freigegeben, z. B. der Schlüsselspeicher für kryptografische Vorgänge mit hardwaregestützten Schlüsseln. Insgesamt sollten Entwickler Microdroid als vertraute Umgebung mit den Tools empfinden, die sie im vollständigen Android-Betriebssystem gewohnt sind.

Funktionen

Microdroid ist eine vereinfachte Version von Android mit einigen zusätzlichen, speziell für pVMs spezifischen Komponenten. Microdroid unterstützt:

  • Eine Teilmenge der NDK-APIs (alle APIs für die Android-Implementierung von libc und Bionic werden bereitgestellt)
  • Debugging-Funktionen wie adb, logcat, tombstone und gdb
  • Verifizierter Bootmodus und SELinux
  • Laden und Ausführen eines Binärprogramms zusammen mit gemeinsam genutzten Bibliotheken, die in ein APK eingebettet sind
  • Binder-RPC über vsock und Austausch von Dateien mit impliziten Integritätsprüfungen
  • Laden von APEX-Objekten

Microdroid unterstützt Folgendes nicht:

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

  • SystemServer und Zygote

  • Grafiken/Benutzeroberfläche

  • HALs

Microdroid-Architektur

Microdroid ähnelt Cuttlefish, da beide eine Architektur haben, die dem Standard-Android ähnelt. Microdroid besteht aus den folgenden Partitions-Images, die in einem zusammengesetztes Laufwerk-Image gruppiert sind:

  • bootloader – Prüft und startet den Kernel.
  • boot.img: Enthält den Kernel und das Init-Ramdisk.
  • vendor_boot.img: Enthält VM-spezifische Kernelmodule 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 geliefert und von VirtualizationService in einem zusammengesetzten Laufwerk-Image verpackt. Zusätzlich zum Haupt-Composite-Laufwerk-Image des Betriebssystems ist VirtualizationService für die Erstellung der folgenden 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 pro Instanz geprüften Boot-Daten, z. B. pro Instanz gültiger Salt, vertrauenswürdige APEX-öffentliche Schlüssel und Rollback-Zähler

Bootreihenfolge

Die Startsequenz von Microdroid erfolgt nach dem Starten des Geräts. Der Gerätestart wird im Abschnitt zur pVM-Firmware des Dokuments Architektur beschrieben. Abbildung 1 zeigt die Schritte, die während der Microdroid-Bootsequenz ausgeführt werden:

Sicherer Bootflow der Mikrodroid-Instanz

Abbildung 1. Secure Boot-Ablauf der Microdroid-Instanz

Hier eine Erklärung der einzelnen Schritte:

  1. Der Bootloader wird von crosvm in den Arbeitsspeicher geladen und pvmfw wird ausgeführt. Bevor pvmfw zum Bootloader springt, führt er zwei Aufgaben aus:

    • Prüft, ob der Bootloader von einer vertrauenswürdigen Quelle (Google oder ein OEM) stammt.
    • Sorgt durch die Nutzung des Instanz-Images dafür, dass derselbe Bootloader bei mehreren Booten derselben pVM konsistent verwendet wird. Insbesondere wird die pVM anfänglich mit einem leeren Instanz-Image gestartet. pvmfw speichert die Identität des Bootloaders im Instanz-Image und verschlüsselt sie. Wenn die pVM also 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 es sich um dieselbe Identität handelt, die zuvor gespeichert wurde. Wenn die Identitäten nicht übereinstimmen, startet pvmfw nicht.

    Der Bootloader startet dann Microdroid.

  2. Der Bootloader greift auf das Instanzlaufwerk zu. Ähnlich wie pvmfw hat der Bootloader ein Instanzlaufwerk mit Informationen zu Partitions-Images, die in dieser Instanz bei früheren Starts 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 übernimmt Microdroid die Kontrolle an den Kernel.

  4. Da die Superpartition bereits vom Bootloader verifiziert wurde (Schritt 3), stellt der Kernel die Superpartition bedingungslos bereit. Wie bei der vollständigen Android-Version 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-Script ähnelt dem von Android, ist aber auf die Anforderungen von Microdroid zugeschnitten.

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

  6. Der Microdroid-Manager-Dienst beginnt am apexd.

  7. apexd stellt die APEXes in den Verzeichnissen /apex/<name> bereit. Der einzige Unterschied zwischen der Bereitstellung von APEX-Dateien unter Android und Microdroid besteht darin, dass die APEX-Dateien unter 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 das Client-APK bereit, bei dem es sich im Wesentlichen um eine ZIP-Datei als Dateisystem handelt. Die APK-Datei wird von der pVM mit dm-verity als virtuelles Blockgerät übergeben, genau wie bei APEX. Die APK enthält eine Konfigurationsdatei mit einer Liste der APEXes, die der App-Entwickler für diese PVM-Instanz angefordert hat. Die Liste wird von apexd verwendet, wenn APEX-Einträge aktiviert werden.

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

Dateiaustausch (AuthFS)

Android-Komponenten verwenden häufig Dateien für Eingabe, Ausgabe und Status und geben diese als Dateideskriptoren (ParcelFileDescriptor typische AIDL-Dateien) weiter, deren Zugriff vom Android-Kernel gesteuert wird. AuthFS bietet ähnliche Funktionen für den Austausch von Dateien zwischen sich gegenseitig nicht vertrauenden Endpunkten über pVM-Grenzen hinweg.

AuthFS ist im Grunde ein Remote-Dateisystem mit transparenten Integritätsprüfungen für einzelne Zugriffsvorgänge, ähnlich wie fs-verity. Durch die Prüfungen kann das Frontend, z. B. ein in einer pVM ausgeführtes Programm zum Lesen von Dateien, feststellen, ob das nicht vertrauenswürdige Backend, normalerweise Android, Dateiinhalte manipuliert hat.

Zum Austausch von Dateien wird das Back-End (fd\_server) mit einer dateispezifischen Konfiguration gestartet, die angibt, ob es für die Eingabe (schreibgeschützt) oder die Ausgabe (Lese-/Schreibzugriff) bestimmt ist. Für die Eingabe erzwingt das Front-End, dass der Inhalt mit einem bekannten Hash-Wert auf einem Merkle-Baum zur Überprüfung bei Zugriff übereinstimmt. Für die Ausgabe behält AuthFS intern einen Hashbaum des von Schreibvorgängen beobachteten Inhalts bei und kann die Integrität beim Zurücklesen der Daten erzwingen.

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 Siegelschlüssel zur Sicherung persistenter Daten und einem Attestationsschlüssel zur Erstellung von Signaturen bereitgestellt, die nachweislich von der pVM stammen.

Binder RPC

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

Zur Einrichtung der Verbindung erstellt ein Endpunkt, z. B. die pVM-Nutzlast, ein RpcServer-Objekt, registriert ein Stammobjekt und beginnt, auf neue Verbindungen zu warten. Clients können über ein RpcSession-Objekt eine Verbindung zu diesem Server herstellen, das Binder-Objekt abrufen und genau so verwenden, wie ein Binder-Objekt mit dem Kernel-Binder-Treiber verwendet wird.