Android bietet eine Referenzimplementierung aller Komponenten, die für die Implementierung des Android Virtualization-Frameworks erforderlich sind. Derzeit ist diese Implementierung auf ARM64 beschränkt. Auf dieser Seite wird die Framework-Architektur erläutert.
Hintergrund
Die Arm-Architektur ermöglicht bis zu vier Ausnahmeebenen, wobei die Ausnahmestufe 0 (EL0) die am wenigsten privilegierte und die Ausnahmestufe 3 (EL3) die größte ist. Der größte Teil der Android-Codebasis (alle Userspace-Komponenten) wird bei EL0 ausgeführt. Der Rest dessen, was allgemein als „Android“ bezeichnet wird, ist der Linux-Kernel, der bei EL1 ausgeführt wird.
Die EL2-Ebene ermöglicht die Einführung eines Hypervisors, mit dem sich Arbeitsspeicher und Geräte in einzelne pVMs auf EL1/EL0 mit starken Vertraulichkeits- und Integritätsgarantien isolieren lassen.
Hypervisor
Die geschützte kernelbasierte virtuelle Maschine (pKVM) basiert auf dem Linux KVM-Hypervisor, der um die Möglichkeit erweitert wurde, den Zugriff auf die Nutzlasten einzuschränken, die in Gast-VMs ausgeführt werden, die beim Erstellen als „geschützt“ gekennzeichnet wurden.
KVM/arm64 unterstützt je nach Verfügbarkeit bestimmter CPU-Funktionen unterschiedliche Ausführungsmodi, nämlich die Virtualisierungs-Hosterweiterungen (VHE) (ARMv8.1 und höher). In einem dieser Modi, der allgemein als Nicht-VHE-Modus bezeichnet wird, wird der Hypervisor-Code während des Starts aus dem Kernel-Image geteilt und unter EL2 installiert, während der Kernel selbst unter EL1 ausgeführt wird. Die EL2-Komponente von KVM ist zwar Teil der Linux-Codebasis, aber eine kleine Komponente, die für den Wechsel zwischen mehreren EL1s verantwortlich ist. Die Hypervisor-Komponente wird mit Linux kompiliert, befindet sich aber in einem separaten, speziellen Arbeitsspeicherbereich des vmlinux
-Images. pKVM nutzt dieses Design, indem der Hypervisor-Code um neue Funktionen erweitert wird, mit denen der Android-Hostkernel und der Nutzerbereich eingeschränkt und der Hostzugriff auf den Gastspeicher und den Hypervisor begrenzt werden können.
pKVM-Anbietermodule
Ein pKVM-Anbietermodul ist ein hardwarespezifisches Modul mit gerätespezifischen Funktionen wie IOMMU-Treibern (Input-Output Memory Management Unit). Mit diesen Modulen können Sie Sicherheitsfunktionen, für die ein Zugriff auf die Ausnahmeebene 2 (EL2) erforderlich ist, auf pKVM portieren.
Informationen zum Implementieren und Laden eines pKVM-Anbietermoduls finden Sie unter pKVM-Anbietermodul implementieren.
Bootvorgang
Die folgende Abbildung zeigt den Bootvorgang von pKVM:
- Der Bootloader betritt den generischen Kernel bei EL2.
- Der generische Kernel erkennt, dass er auf EL2 ausgeführt wird, und hebt die Berechtigungen für EL1 auf, während pKVM und seine Module weiterhin auf EL2 ausgeführt werden. Außerdem werden derzeit pKVM-Anbietermodule geladen.
- Der generische Kernel startet normal und lädt alle erforderlichen Gerätetreiber, bis der Nutzerspeicherplatz erreicht ist. Zu diesem Zeitpunkt ist pKVM vorhanden und verarbeitet die Seitentabellen der Stufe 2.
Beim Bootvorgang wird dem Bootloader nur zu Beginn des Starts vertraut, um die Integrität des Kernel-Images aufrechtzuerhalten. Wenn dem Kernel die Berechtigungen entzogen wurden, wird er vom Hypervisor nicht mehr als vertrauenswürdig eingestuft. Dieser ist dann selbst für den Schutz des Kernels verantwortlich, wenn er manipuliert wurde.
Da sich der Android-Kernel und der Hypervisor im selben Binär-Image befinden, ist eine sehr eng verbundene Kommunikationsschnittstelle zwischen ihnen möglich. Diese enge Kopplung garantiert atomare Aktualisierungen der beiden Komponenten, sodass die Schnittstelle zwischen ihnen nicht stabil gehalten werden muss. Außerdem bietet sie eine große Flexibilität, ohne die langfristige Wartbarkeit zu beeinträchtigen. Die enge Kopplung ermöglicht auch Leistungsoptimierungen, wenn beide Komponenten zusammenarbeiten können, ohne die vom Hypervisor bereitgestellten Sicherheitsgarantien zu beeinträchtigen.
Außerdem ermöglicht die Einführung von GKI im Android-System automatisch die Bereitstellung des pKVM-Hypervisors auf Android-Geräten im selben Binärcode wie der Kernel.
Schutz vor Zugriffen auf den CPU-Speicher
Die Arm-Architektur spezifiziert eine Speicherverwaltungseinheit (Memory Management Unit, MU), die in zwei unabhängige Phasen aufgeteilt ist. Beide Phasen können zur Implementierung von Adressübersetzung und Zugriffssteuerung für verschiedene Teile des Arbeitsspeichers verwendet werden. Die MMU der Stufe 1 wird von EL1 gesteuert und ermöglicht eine Adressübersetzung der ersten Ebene. Die MMU der Stufe 1 wird von Linux verwendet, um den virtuellen Adressraum zu verwalten, der jedem Userspace-Prozess und seinem eigenen virtuellen Adressraum zur Verfügung gestellt wird.
Die MMU der Stufe 2 wird von EL2 gesteuert und ermöglicht die Anwendung einer zweiten Adressübersetzung auf die Ausgabeadresse der MMU der Stufe 1, was zu einer physischen Adresse (PA) führt. Die Übersetzung von Phase 2 kann von Hypervisoren verwendet werden, um Speicherzugriffe von allen Gast-VMs zu steuern und zu übersetzen. Wie in Abbildung 2 dargestellt, wird die Ausgabeadresse der ersten Phase als Zwischen-Physische-Adresse (IPA) bezeichnet, wenn beide Übersetzungsphasen aktiviert sind. Hinweis: Die virtuelle Adresse (VA) wird in eine IPA und dann in eine PA umgewandelt.
Bisher wurde KVM mit aktivierter Übersetzungsstufe 2 beim Ausführen von Gästen und deaktivierter Übersetzungsstufe 2 beim Ausführen des Linux-Kernels des Hosts ausgeführt. Diese Architektur ermöglicht es, dass Speicherzugriffe von der MMU der ersten Hoststufe durch die MMU der zweiten Stufe geleitet werden, was einen uneingeschränkten Zugriff vom Host auf die Speicherseiten des Gastes ermöglicht. Andererseits aktiviert pKVM den Schutz der Stufe 2 auch im Hostkontext und übernimmt den Hypervisor für den Schutz der Gastspeicherseiten anstelle des Hosts.
KVM nutzt die Adressübersetzung in Phase 2 voll aus, um komplexe IPA/PA-Zuordnungen für Gäste zu implementieren. Dadurch entsteht trotz physischer Fragmentierung der Anschein eines zusammenhängenden Arbeitsspeichers für Gäste. Die Verwendung der MMU der Stufe 2 für den Host ist jedoch nur auf die Zugriffssteuerung beschränkt. Die Host-Phase 2 ist identitätszugeordnet, damit der zusammenhängende Speicher im IPA-Bereich des Hosts auch im PA-Bereich zusammenhängend ist. Diese Architektur ermöglicht die Verwendung großer Zuordnungen in der Seitentabelle und reduziert so den Druck auf den Translation Lookaside Buffer (TLB). Da eine Identitätszuordnung durch PA indexiert werden kann, wird die Host-Phase 2 auch verwendet, um die Seiteneigentümerschaft direkt in der Seitentabelle zu verfolgen.
DMA-Schutz (Direct Memory Access)
Wie bereits erwähnt, ist das Aufheben der Zuordnung von Gastseiten vom Linux-Host in den CPU-Seitentabellen ein notwendiger, aber nicht ausreichender Schritt zum Schutz des Gastspeichers. pKVM muss auch vor Speicherzugriffen durch DMA-fähige Geräte schützen, die vom Hostkernel gesteuert werden, und vor der Möglichkeit eines DMA-Angriffs, der von einem schädlichen Host initiiert wird. Um zu verhindern, dass ein solches Gerät auf den Gastspeicher zugreift, benötigt pKVM für jedes DMA-fähige Gerät im System eine IOMMU-Hardware (Input-Output Memory Management Unit), wie in Abbildung 3 dargestellt.
IOMMU-Hardware bietet mindestens die Möglichkeit, Lese-/Schreibzugriff für ein Gerät auf den physischen Arbeitsspeicher auf Seitenebene zu gewähren und zu widerrufen. Diese IOMMU-Hardware schränkt jedoch die Verwendung von Geräten in pVMs ein, da diese von einer Phase 2 der Identitätszuweisung ausgehen.
Um die Isolation zwischen virtuellen Maschinen zu gewährleisten, müssen Arbeitsspeichertransaktionen, die im Namen verschiedener Entitäten generiert werden, von der IOMMU unterschieden werden können, damit die entsprechenden Seitentabellen für die Übersetzung verwendet werden können.
Außerdem ist die Reduzierung der Menge an SoC-spezifischem Code bei EL2 eine wichtige Strategie zur Verringerung der gesamten Trusted Computing Base (TCB) von pKVM. Dies steht im Widerspruch zur Einbeziehung von IOMMU-Treibern in den Hypervisor. Um dieses Problem zu vermeiden, ist der Host bei EL1 für zusätzliche IOMMU-Verwaltungsaufgaben wie die Energieverwaltung, die Initialisierung und gegebenenfalls die Interrupt-Verarbeitung verantwortlich.
Wenn der Host jedoch die Kontrolle über den Gerätestatus übernimmt, stellt dies zusätzliche Anforderungen an die Programmierschnittstelle der IOMMU-Hardware, damit Berechtigungsprüfungen nicht auf andere Weise umgangen werden können, z. B. nach einem Geräteneustart.
Eine standardmäßige und gut unterstützte IOMMU für Arm-Geräte, die sowohl Isolation als auch direkte Zuweisung ermöglicht, ist die Arm System Memory Management Unit (SMMU)-Architektur. Diese Architektur ist die empfohlene Referenzlösung.
Arbeitsspeicherinhaberschaft
Beim Start wird davon ausgegangen, dass der gesamte nicht dem Hypervisor zugewiesene Arbeitsspeicher dem Host gehört und wird vom Hypervisor entsprechend erfasst. Wenn eine pVM gestartet wird, spendet der Host Speicherseiten, damit sie gestartet werden kann. Der Hypervisor überträgt die Eigentümerschaft dieser Seiten vom Host auf die pVM. Daher erzwingt der Hypervisor Zugriffssteuerungsbeschränkungen in der Seitentabelle der Stufe 2 des Hosts, um zu verhindern, dass er noch einmal auf die Seiten zugreift. So wird die Vertraulichkeit für den Gast gewahrt.
Die Kommunikation zwischen Host und Gästen wird durch eine kontrollierte gemeinsame Nutzung des Arbeitsspeichers ermöglicht. Gäste können einige ihrer Seiten über einen Hypervisor mit dem Host teilen. Dieser weist den Hypervisor an, diese Seiten in der Seitentabelle für Hostphase 2 neu zuzuordnen. Ebenso wird die Kommunikation des Hosts mit TrustZone durch Speicherfreigabe- und/oder -leihvorgänge ermöglicht, die alle von pKVM anhand der Firmware Framework for Arm (FF-A)-Spezifikation genau überwacht und gesteuert werden.
Da sich die Speicheranforderungen einer pVM im Laufe der Zeit ändern können, wird ein Hyperaufruf bereitgestellt, der es dem Host ermöglicht, die Inhaberschaft bestimmter Seiten, die dem Aufrufer gehören, zurückzugeben. In der Praxis wird dieser Hypercall mit dem Virtio-Balloon-Protokoll verwendet, damit die VMM Speicher von der pVM zurückfordern und die pVM die VMM auf kontrollierte Weise über freigegebene Seiten informieren kann.
Der Hypervisor ist dafür verantwortlich, das Eigentum an allen Speicherseiten im System zu verwalten und zu verfolgen, ob sie für andere Entitäten freigegeben oder ausgeliehen werden. Der Großteil dieses Status-Trackings erfolgt mithilfe von Metadaten, die an die Seitentabellen der Stufe 2 des Hosts und der Gäste angehängt sind. Dabei werden reservierte Bits in den Seitentabelleneinträgen (PTEs) verwendet, die, wie der Name schon sagt, für die Softwarenutzung reserviert sind.
Der Host darf nicht versuchen, auf Seiten zuzugreifen, die vom Hypervisor unzugänglich gemacht wurden. Ein illegaler Hostzugriff führt dazu, dass der Hypervisor eine synchrone Ausnahme in den Host einschleust. Dies kann entweder dazu führen, dass die zuständige Userspace-Aufgabe ein SEGV-Signal empfängt, oder dass der Host-Kernel abstürzt. Um versehentliche Zugriffe zu verhindern, werden Seiten, die Gästen zur Verfügung gestellt werden, vom Host-Kernel für das Auslagern oder Zusammenführen deaktiviert.
Unterbrechungsbehandlung und Timer
Unterbrechungen sind ein wesentlicher Bestandteil der Interaktion eines Gastes mit Geräten und der Kommunikation zwischen CPUs, wobei Interprozessorunterbrechungen (IPIs) der Hauptkommunikationsmechanismus sind. Das KVM-Modell besteht darin, die gesamte Verwaltung virtueller Unterbrechungen an den Host in EL1 zu delegieren, der sich zu diesem Zweck als nicht vertrauenswürdiger Teil des Hypervisors verhält.
pKVM bietet eine vollständige GICv3-Emulation (Generic Interrupt Controller Version 3) basierend auf dem vorhandenen KVM-Code. Timer und IPIs werden als Teil dieses nicht vertrauenswürdigen Emulation-Codes verarbeitet.
GICv3-Support
Über die Schnittstelle zwischen EL1 und EL2 muss der vollständige Unterbrechungsstatus für den EL1-Host sichtbar sein, einschließlich Kopien der Hypervisor-Register im Zusammenhang mit Unterbrechungen. Diese Sichtbarkeit wird in der Regel mithilfe von gemeinsam genutzten Speicherregionen erreicht, eine pro virtueller CPU (vCPU).
Der Code zur Laufzeitunterstützung des Systemregisters kann vereinfacht werden, sodass nur die Registrierung von Interrupts im Software Generated Interrupt Register (SGIR) und Deactivate Interrupt Register (DIR) unterstützt wird. Die Architektur schreibt vor, dass diese Register immer zu EL2 führen, während die anderen Traps bisher nur zur Behebung von Fehlern nützlich waren. Alles andere wird in der Hardware verarbeitet.
Auf der MMIO-Seite wird alles auf EL1 emuliert, wobei die gesamte aktuelle Infrastruktur in KVM wiederverwendet wird. Schließlich wird Wait for Interrupt (WFI) immer an EL1 weitergeleitet, da dies eine der grundlegenden Scheduling-Primitiven ist, die KVM verwendet.
Timerunterstützung
Der Vergleichswert für den virtuellen Timer muss EL1 bei jedem Abfang-WFI verfügbar gemacht werden, damit EL1 Timer-Unterbrechungen einfügen kann, während die vCPU blockiert ist. Der physische Timer wird vollständig emuliert und alle Fallen werden an EL1 weitergeleitet.
MMIO-Verarbeitung
Um mit dem Virtual Machine Monitor (VMM) zu kommunizieren und die GIC-Emulation auszuführen, müssen MMIO-Traps zur weiteren Bearbeitung an den Host in EL1 weitergeleitet werden. Für pKVM ist Folgendes erforderlich:
- IPA und Größe des Zugriffs
- Daten bei Schreibvorgang
- Endianness der CPU zum Zeitpunkt des Trappings
Außerdem werden Fallen mit einem GPR (General Purpose Register) als Quelle/Ziel über ein abstraktes Übertragungs-Pseudoregister weitergeleitet.
Gastoberflächen
Ein Gast kann mit einem geschützten Gast über eine Kombination aus Hypercalls und Speicherzugriff auf gefangene Regionen kommunizieren. Hypercalls werden gemäß dem SMCCC-Standard bereitgestellt. Dabei ist ein Bereich für die Zuweisung durch einen Anbieter durch KVM reserviert. Die folgenden Hyperaufrufe sind für pKVM-Gäste von besonderer Bedeutung.
Generische Hypercalls
- PSCI bietet dem Gast einen Standardmechanismus zur Steuerung des Lebenszyklus seiner vCPUs, einschließlich Ein- und Ausschalten und Systemabschaltung.
- TRNG stellt einen Standardmechanismus für den Gast bereit, um Entropie von der pKVM anzufordern, die den Aufruf an EL3 weiterleitet. Dieser Mechanismus ist besonders nützlich, wenn der Host nicht vertrauenswürdig ist, um einen Hardware-Zufallszahlengenerator (RNG) zu virtualisieren.
pKVM-Hypercalls
- Speicherfreigabe mit dem Host Der gesamte Gastspeicher ist für den Host anfangs nicht zugänglich. Der Hostzugriff ist jedoch für die Kommunikation mit gemeinsam genutztem Arbeitsspeicher und für paravirtualisierte Geräte erforderlich, die auf freigegebenen Puffern basieren. Mit Hypercalls zum Freigeben und Entfernen von Seiten für den Host kann der Gast genau festlegen, welche Speicherbereiche für den Rest von Android zugänglich gemacht werden, ohne dass ein Handshake erforderlich ist.
- Freigabe von Arbeitsspeicher an den Host Der gesamte Gastspeicher gehört in der Regel dem Gast, bis er gelöscht wird. Dieser Zustand kann für langlebige VMs mit Speicheranforderungen, die sich im Laufe der Zeit ändern, nicht ausreichend sein. Mit dem
relinquish
-Hypercall kann ein Gast die Inhaberschaft von Seiten explizit an den Host zurückgeben, ohne dass der Gast beendet werden muss. - Speicherzugriffs-Trapping auf dem Host. Wenn ein KVM-Gast auf eine Adresse zugreift, die keiner gültigen Speicherregion entspricht, wird der vCPU-Thread zum Host ausgetreten. Der Zugriff wird normalerweise für MMIO verwendet und vom VMM im Nutzerbereich emuliert. Um diese Verarbeitung zu ermöglichen, muss pKVM dem Host Details zur fehlerhaften Anweisung wie ihre Adresse, Registerparameter und möglicherweise ihren Inhalt mitteilen. Dies kann dazu führen, dass vertrauliche Daten aus einem geschützten Gast unbeabsichtigt freigegeben werden, wenn die Falle nicht vorhergesehen wurde. pKVM löst dieses Problem, indem es diese Fehler als fatal behandelt, es sei denn, der Gast hat zuvor einen Hypercall ausgeführt, um den fehlerhaften IPA-Bereich als einen zu identifizieren, für den Zugriffe an den Host zurückgesendet werden dürfen. Diese Lösung wird als MMIO-Guard bezeichnet.
Virtuelles E/A-Gerät (Virtio)
Virtio ist ein beliebter, portabler und ausgereifter Standard für die Implementierung paravirtualisierter Geräte und die Interaktion mit ihnen. Die meisten Geräte, die für geschützte Gäste freigegeben sind, werden mit virtio implementiert. Virtio unterstützt auch die vsock-Implementierung, die für die Kommunikation zwischen einem geschützten Gast und dem Rest von Android verwendet wird.
Virtio-Geräte werden in der Regel vom VMM im Nutzerbereich des Hosts implementiert. Dieses fängt gefangene Speicherzugriffe vom Gast an die MMIO-Schnittstelle des Virtio-Geräts ab und emuliert das erwartete Verhalten. Der MMIO-Zugriff ist relativ teuer, da jeder Zugriff auf das Gerät einen Hin- und Rückweg zur VMM erfordert. Daher erfolgt der Großteil der tatsächlichen Datenübertragung zwischen Gerät und Gast über eine Reihe von VirtQueues im Arbeitsspeicher. Eine wichtige Annahme von virtio ist, dass der Host beliebig auf den Gastspeicher zugreifen kann. Diese Annahme ist im Design der VirtQueue zu sehen, die Verweise auf Puffer im Gast enthalten kann, auf die die Geräteemulation direkt zugreifen soll.
Die zuvor beschriebenen Hypercalls zur gemeinsamen Nutzung von Arbeitsspeicher können zwar verwendet werden, um Virtio-Datenbuffer vom Gast an den Host freizugeben, diese Freigabe erfolgt jedoch auf Seitenebene und es werden möglicherweise mehr Daten freigegeben als erforderlich, wenn die Buffergröße kleiner als die einer Seite ist. Stattdessen ist der Gast so konfiguriert, dass sowohl die VirtQueues als auch die entsprechenden Datenbuffer aus einem festen Fenster im gemeinsamen Speicher zugewiesen werden. Die Daten werden bei Bedarf in das Fenster kopiert (gepuffert) und aus dem Fenster zurückgelesen.
Interaktion mit TrustZone
Auch wenn Gäste nicht direkt mit TrustZone interagieren können, muss der Host weiterhin SMC-Anrufe in die sichere Umgebung senden können. Diese Aufrufe können physisch adressierte Speicherpuffer angeben, die für den Host nicht zugänglich sind. Da die sichere Software in der Regel nicht über die Zugänglichkeit des Buffers informiert ist, kann ein böswilliger Host diesen Puffer verwenden, um einen Confused Deputy-Angriff (analog zu einem DMA-Angriff) durchzuführen. Um solche Angriffe zu verhindern, fängt pKVM alle SMC-Aufrufe des Hosts an EL2 ab und fungiert als Proxy zwischen dem Host und dem sicheren Monitor auf EL3.
PSCI-Anrufe vom Host werden mit minimalen Änderungen an die EL3-Firmware weitergeleitet. Insbesondere wird der Einstiegspunkt für eine CPU, die online kommt oder aus dem Ruhemodus fortgesetzt wird, umgeschrieben, damit die Seitentabelle der Stufe 2 bei EL2 installiert wird, bevor sie zu EL1 zurückkehrt. Während des Bootens wird dieser Schutz von pKVM erzwungen.
Diese Architektur setzt voraus, dass das SoC PSCI unterstützt, vorzugsweise durch die Verwendung einer aktuellen Version von TF-A als EL3-Firmware.
Das Firmware Framework for Arm (FF-A) standardisiert die Interaktionen zwischen der normalen und der sicheren Welt, insbesondere bei einem sicheren Hypervisor. Ein wesentlicher Teil der Spezifikation definiert einen Mechanismus zum Freigeben von Arbeitsspeicher für die sichere Umgebung. Dabei werden sowohl ein gängiges Nachrichtenformat als auch ein klar definiertes Berechtigungsmodell für die zugrunde liegenden Seiten verwendet. pKVM stellt FF-A-Nachrichten als Proxy bereit, damit der Host nicht versucht, Arbeitsspeicher für die sichere Seite freizugeben, für die er nicht über ausreichende Berechtigungen verfügt.
Bei dieser Architektur wird die Secure World-Software verwendet, um das Speicherzugriffsmodell durchzusetzen. So wird sichergestellt, dass vertrauenswürdige Apps und andere Software, die in der Secure World ausgeführt wird, nur dann auf den Speicher zugreifen können, wenn er entweder ausschließlich der Secure World gehört oder ihr explizit mit FF-A freigegeben wurde. Auf einem System mit S-EL2 sollte die Durchsetzung des Speicherzugriffsmodells durch einen Secure Partition Manager Core (SPMC) wie Hafnium erfolgen, der Seitentabellen der Stufe 2 für die sichere Welt verwaltet. Auf einem System ohne S-EL2 kann das TEE stattdessen ein Speicherzugriffsmodell über seine Seitentabellen der Stufe 1 erzwingen.
Wenn der SMC-Aufruf an EL2 kein PSCI-Aufruf oder eine von FF-A definierte Nachricht ist, werden nicht verarbeitete SMCs an EL3 weitergeleitet. Es wird davon ausgegangen, dass die (unbedingt vertrauenswürdige) sichere Firmware nicht verarbeitete SMCs sicher verarbeiten kann, da die Firmware die Vorsichtsmaßnahmen kennt, die zur Aufrechterhaltung der pVM-Isolation erforderlich sind.
Virtual Machine Monitor
crosvm ist ein Virtual Machine Monitor (VMM), der virtuelle Maschinen über die KVM-Schnittstelle von Linux ausführt. Was crosvm einzigartig macht, ist sein Fokus auf Sicherheit durch die Verwendung der Programmiersprache Rust und einer Sandbox um virtuelle Geräte, um den Host-Kernel zu schützen. Weitere Informationen zu crosvm finden Sie in der offiziellen Dokumentation.
Dateideskriptoren und ioctls
KVM macht das /dev/kvm
-Zeichengerät dem Nutzerbereich mit ioctls zur Verfügung, aus denen die KVM API besteht. Die ioctls gehören zu den folgenden Kategorien:
- System-ioctls fragen globale Attribute ab, die sich auf das gesamte KVM-Subsystem auswirken, und erstellen pVMs.
- VM-ioctls rufen Attribute ab und legen sie fest, die virtuelle CPUs (vCPUs) und Geräte erstellen und sich auf eine gesamte pVM auswirken, z. B. das Speicherlayout und die Anzahl der virtuellen CPUs (vCPUs) und Geräte.
- Mit vCPU-ioctls werden Attribute abgefragt und festgelegt, die den Betrieb einer einzelnen virtuellen CPU steuern.
- Geräte-ioctls fragen Attribute ab und legen sie fest, die den Betrieb eines einzelnen virtuellen Geräts steuern.
In jedem crosvm-Prozess wird genau eine Instanz einer virtuellen Maschine ausgeführt. Dabei wird das KVM_CREATE_VM
-System-ioctl verwendet, um einen VM-Dateideskriptor zu erstellen, mit dem pVM-ioctls ausgegeben werden können. Mit einem KVM_CREATE_VCPU
- oder KVM_CREATE_DEVICE
-ioctl auf einem VM-FD wird eine vCPU/ein Gerät erstellt und ein Dateideskriptor zurückgegeben, der auf die neue Ressource verweist. Mit ioctls auf einer vCPU oder einem Geräte-FD kann das Gerät gesteuert werden, das mit dem ioctl auf einem VM-FD erstellt wurde. Bei vCPUs gehört dies auch zum Ausführen von Gastcode.
Intern registriert crosvm die Dateideskriptoren der VM über die kantengesteuerte epoll
-Schnittstelle beim Kernel. Der Kernel benachrichtigt dann crosvm, wenn in einem der Dateideskriptoren ein neues Ereignis ansteht.
pKVM fügt die neue Funktion KVM_CAP_ARM_PROTECTED_VM
hinzu, mit der Informationen zur pVM-Umgebung abgerufen und der geschützte Modus für eine VM eingerichtet werden können. Wenn das Flag --protected-vm
übergeben wird, verwendet crosvm diese Funktion bei der Erstellung von pVMs, um die entsprechende Menge an Arbeitsspeicher für die pVM-Firmware abzufragen und zu reservieren und dann den geschützten Modus zu aktivieren.
Arbeitsspeicherzuweisung
Eine der Hauptaufgaben eines VMM besteht darin, den Arbeitsspeicher der VM zuzuweisen und das Arbeitsspeicherlayout zu verwalten. Crosvm generiert ein festes Arbeitsspeicherlayout, das in der folgenden Tabelle grob beschrieben wird.
FDT im Normalmodus | PHYS_MEMORY_END - 0x200000
|
Speicher freigeben | ...
|
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000)
|
Kernel | 0x80080000
|
Bootloader | 0x80200000
|
FDT im BIOS-Modus | 0x80000000
|
Basis des physischen Arbeitsspeichers | 0x80000000
|
pVM-Firmware | 0x7FE00000
|
Gerätespeicher | 0x10000 - 0x40000000
|
Der physische Arbeitsspeicher wird mit mmap
zugewiesen und der Arbeitsspeicher wird der VM zur Verfügung gestellt, um die als memslots bezeichneten Speicherbereiche mit dem ioctl KVM_SET_USER_MEMORY_REGION
zu füllen. Der gesamte Arbeitsspeicher der Gast-pVM wird daher der Crosvm-Instanz zugewiesen, die ihn verwaltet. Dies kann dazu führen, dass der Prozess beendet wird (VM wird beendet), wenn dem Host der kostenlose Arbeitsspeicher ausgeht. Wenn eine VM beendet wird, wird der Arbeitsspeicher automatisch vom Hypervisor gelöscht und an den Hostkernel zurückgegeben.
Bei regulärer KVM behält der VMM den Zugriff auf den gesamten Gastspeicher. Bei pKVM wird der Gastspeicher aus dem physischen Adressraum des Hosts entfernt, wenn er dem Gast zur Verfügung gestellt wird. Die einzige Ausnahme ist Speicher, der vom Gast explizit freigegeben wird, z. B. für Virtio-Geräte.
MMIO-Regionen im Adressraum des Gastes bleiben nicht zugeordnet. Der Zugriff des Gastes auf diese Regionen wird abgefangen und führt zu einem E/A-Ereignis auf dem VM-FD. Mit diesem Mechanismus werden virtuelle Geräte implementiert. Im geschützten Modus muss der Gast mithilfe eines Hypercalls bestätigen, dass ein Bereich seines Adressraums für MMIO verwendet wird, um das Risiko von versehentlichen Datenlecks zu verringern.
Planung
Jede virtuelle CPU wird durch einen POSIX-Thread dargestellt und vom Linux-Scheduler des Hosts geplant. Der Thread ruft das ioctl KVM_RUN
auf der vCPU-FD auf, was dazu führt, dass der Hypervisor zum Gast-vCPU-Kontext wechselt. Der Host-Scheduler berücksichtigt die Zeit, die in einem Gastkontext verbracht wurde, als Zeit, die vom entsprechenden vCPU-Thread verwendet wurde. KVM_RUN
wird zurückgegeben, wenn ein Ereignis auftritt, das von der VMM verarbeitet werden muss, z. B. I/O, Ende des Interrupts oder Anhalten der vCPU. Das VMM verarbeitet das Ereignis und ruft KVM_RUN
noch einmal auf.
Während KVM_RUN
kann der Thread vom Host-Scheduler unterbrochen werden, mit Ausnahme der Ausführung des EL2-Hypervisor-Codes, der nicht unterbrochen werden kann. Die Gast-pVM selbst hat keinen Mechanismus zum Steuern dieses Verhaltens.
Da alle vCPU-Threads wie alle anderen Userspace-Aufgaben geplant werden, unterliegen sie allen Standard-QoS-Mechanismen. Insbesondere kann jeder vCPU-Thread auf physische CPUs ausgerichtet, in CPU-Sets platziert, mithilfe der Auslastungsbegrenzung beschleunigt oder begrenzt, die Prioritäts-/Planungsrichtlinie geändert werden und vieles mehr.
Virtuelle Geräte
Crosvm wird auf einer Reihe von Geräten unterstützt, darunter:
- virtio-blk für zusammengesetzte Laufwerk-Images, schreibgeschützt oder Lese-/Schreibzugriff
- vhost-vsock für die Kommunikation mit dem Host
- virtio-pci als Virtio-Transport
- pl030 Realtime Clock (RTC)
- 16550a UART für die serielle Kommunikation
pVM-Firmware
Die pVM-Firmware (pvmfw) ist der erste Code, der von einer pVM ausgeführt wird, ähnlich dem Boot-ROM eines physischen Geräts. Das Hauptziel von pvmfw besteht darin, einen Secure Boot zu starten und das eindeutige Secret der pVM abzuleiten. pvmfw ist nicht auf die Verwendung mit einem bestimmten Betriebssystem wie Microdroid beschränkt, solange das Betriebssystem ordnungsgemäß unterstützt wird und das Betriebssystem ordnungsgemäß signiert ist.
Das pvmfw-Binary wird in einer gleichnamigen Flash-Partition gespeichert und über OTA aktualisiert.
Gerätestart
Dem Bootvorgang eines pKVM-fähigen Geräts wird die folgende Abfolge von Schritten hinzugefügt:
- Der Android-Bootloader (ABL) lädt pvmfw aus seiner Partition in den Arbeitsspeicher und überprüft das Image.
- Die ABL erhält ihre DICE-Geheimnisse (Device Identifier Composition Engine, zusammengesetzte Geräte-IDs und DICE-Zertifikatskette) von einem Root of Trust.
- Der ABL leitet die erforderlichen CDIs für pvmfw ab und hängt sie an die pvmfw-Binärdatei an.
- Die ABL fügt dem DT einen Knoten für einen reservierten Arbeitsspeicherbereich vom Typ
linux,pkvm-guest-firmware-memory
hinzu, der den Speicherort und die Größe der pvmfw-Binärdatei und der im vorherigen Schritt abgeleiteten Geheimnisse beschreibt. - Die ABL übergibt die Kontrolle an Linux und Linux initialisiert pKVM.
- pKVM trennt den pvmfw-Speicherbereich von den Auslagerungstabellen der Stufe 2 des Hosts und schützt ihn während der gesamten Gerätelaufzeit vor dem Host (und Gästen).
Nach dem Gerätestart wird Microdroid gemäß den Schritten im Abschnitt Bootsequenz des Microdroid-Dokuments gestartet.
pVM-Boot
Beim Erstellen einer pVM muss crosvm (oder ein anderes VMM) einen ausreichend großen Memslot erstellen, der vom Hypervisor mit dem pvmfw-Image gefüllt wird. Die VMM ist auch in der Liste der Register eingeschränkt, deren Anfangswert sie festlegen kann (x0–x14 für die primäre vCPU, keine für sekundäre vCPUs). Die übrigen Register sind reserviert und Teil der hypervisor-pvmfw ABI.
Wenn die pVM ausgeführt wird, übergibt der Hypervisor zuerst die Kontrolle über die primäre vCPU an pvmfw. Die Firmware erwartet, dass crosvm einen AVB-signierten Kernel geladen hat, der ein Bootloader oder ein anderes Image sein kann, und eine nicht signierte FDT in den Arbeitsspeicher an bekannten Offset-Positionen. pvmfw validiert die AVB-Signatur und generiert bei Erfolg einen vertrauenswürdigen Gerätebaum aus der empfangenen FDT, löscht die Geheimnisse aus dem Arbeitsspeicher und springt zum Einstiegspunkt der Nutzlast. Wenn einer der Überprüfungsschritte fehlschlägt, gibt die Firmware einen PSCI SYSTEM_RESET
-Hypercall aus.
Zwischen den Starts werden Informationen zur pVM-Instanz in einer Partition (virtio-blk-Gerät) gespeichert und mit dem Secret von pvmfw verschlüsselt, damit das Secret nach einem Neustart der richtigen Instanz bereitgestellt wird.