Android bietet eine Referenzimplementierung aller Komponenten, die zum Implementieren 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 auf EL0 ausgeführt. Der Rest von dem, was allgemein als „Android“ bezeichnet wird, ist der Linux-Kernel, der unter EL1 ausgeführt wird.
Die EL2-Schicht ermöglicht die Einführung eines Hypervisors, der das Isolieren von Arbeitsspeicher und Geräten in einzelnen pVMs bei EL1/EL0 mit hohen Vertraulichkeits- und Integritätsgarantien ermöglicht.
Hypervisor
Die geschützte Kernel-basierte 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 virtuellen Gastmaschinen ausgeführt werden, die zum Zeitpunkt der Erstellung als „geschützt“ gekennzeichnet waren.
KVM/arm64 unterstützt verschiedene Ausführungsmodi, je nach Verfügbarkeit bestimmter CPU-Features, nämlich der Virtualization Host Extensions (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 Bootvorgangs 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 ist mit Linux kompiliert, befindet sich jedoch in einem separaten, dedizierten Speicherabschnitt des vmlinux
-Images. pKVM nutzt dieses Design, indem pKVM den Hypervisor-Code mit neuen Funktionen erweitert, um Einschränkungen für den Android-Host-Kernel und den Nutzerbereich festzulegen und den Hostzugriff auf den Gastarbeitsspeicher und den Hypervisor zu beschränken.
pKVM-Anbietermodule
Ein pKVM-Anbietermodul ist ein hardwarespezifisches Modul, das gerätespezifische Funktionen enthält, z. B. Treiber für die Input-Output-Memory-Management-Einheit (IOMMU). Mit diesen Modulen können Sie Sicherheitsfunktionen portieren, für die EL2-Zugriff (Ausnahmestufe 2) auf pKVM erforderlich ist.
Informationen zum Implementieren und Laden eines pKVM-Anbietermoduls finden Sie unter pKVM-Anbietermodul implementieren.
Bootvorgang
Die folgende Abbildung zeigt den pKVM-Bootvorgang:
- Der Bootloader tritt bei EL2 in den generischen Kernel ein.
- 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 zu diesem Zeitpunkt 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 Bootprozedur vertraut der Bootloader darauf, die Integrität des Kernel-Images nur während eines frühen Starts zu wahren. 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.
Wenn sich der Android-Kernel und der Hypervisor im selben Binär-Image befinden, entsteht eine eng gekoppelte Kommunikationsschnittstelle zwischen ihnen. Diese enge Verbindung garantiert atomare Aktualisierungen der beiden Komponenten, sodass die Schnittstelle zwischen ihnen stabil gehalten werden muss. Außerdem bietet sie ein hohes Maß an Flexibilität, ohne die langfristige Verwaltbarkeit 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.
Darüber hinaus ermöglicht die Einführung von GKI in der Android-Umgebung automatisch die Bereitstellung des pKVM-Hypervisors auf Android-Geräten in derselben Binärdatei wie der Kernel.
Schutz vor Zugriff des CPU-Arbeitsspeichers
Die Arm-Architektur spezifiziert eine Speicherverwaltungseinheit (Memory Management Unit, MU), die in zwei unabhängige Phasen aufgeteilt ist. Beide Phasen können zur Implementierung der Adressübersetzung und der Zugriffssteuerung für verschiedene Teile des Arbeitsspeichers verwendet werden. Die MMU der Phase 1 wird von EL1 gesteuert und ermöglicht eine erste Ebene der Adressübersetzung. Die MMU-Phase 1 wird von Linux verwendet, um den virtuellen Adressbereich zu verwalten, der jedem Nutzerbereichsprozess und seinem eigenen virtuellen Adressbereich zur Verfügung gestellt wird.
Die MMU der Phase 2 wird von EL2 gesteuert und ermöglicht die Anwendung einer zweiten Adressübersetzung auf die Ausgabeadresse der MMU der Phase 1, was zu einer physischen Adresse (PA) führt. Die Übersetzung aus Phase 2 kann von Hypervisoren verwendet werden, um Speicherzugriffe von allen Gast-VMs zu steuern und zu übersetzen. Wie in Abbildung 2 gezeigt, wird die Ausgabeadresse von Phase 1 als eine zwischengeschaltete physische Adresse (IPA) bezeichnet, wenn beide Phasen der Übersetzung aktiviert sind. Hinweis: Die virtuelle Adresse (VA) wird in ein IPA und dann in einen PA übersetzt.
In der Vergangenheit wurde KVM mit aktivierter Übersetzung von Phase 2 ausgeführt, während Gäste ausgeführt wurden, und Phase 2, während der Linux-Kernel des Hosts ausgeführt wurde. Diese Architektur ermöglicht Arbeitsspeicherzugriffe von der MMU der Hostphase 1 durch die MMU-Phase 2 und ermöglicht somit uneingeschränkten Zugriff vom Host auf Gastspeicherseiten. 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 vollständig, um komplexe IPA/PA-Zuordnungen für Gäste zu implementieren. Dadurch entsteht für Gäste trotz physischer Fragmentierung der Eindruck eines zusammenhängenden Speichers. Die Verwendung der MMU der Phase 2 für den Host ist jedoch auf die Zugriffssteuerung beschränkt. Die Hostphase 2 wird anhand der Identität zugeordnet, sodass der zusammenhängende Speicher im Host-IPA-Bereich im PA-Bereich zusammenhängend ist. Diese Architektur ermöglicht die Verwendung großer Zuordnungen in der Seitentabelle und senkt folglich den Druck auf den Lookaside-Zwischenspeicher (TLB). Da eine Identitätszuweisung von PA indexiert werden kann, wird in Hostphase 2 auch die Seiteninhaberschaft direkt in der Seitentabelle nachverfolgt.
DMA-Schutz (Direct Memory Access)
Wie bereits beschrieben, 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 außerdem vor Speicherzugriffen von DMA-fähigen Geräten unter der Kontrolle des Host-Kernels und vor einem DMA-Angriff schützen, der von einem schädlichen Host initiiert wird. Um zu verhindern, dass ein solches Gerät auf den Gastarbeitsspeicher zugreift, benötigt pKVM für jedes DMA-fähige Gerät im System Hardware für die Eingabe-/Ausgabespeicherverwaltung (Input-Output Memory Management Unit, IMMU), wie in Abbildung 3 dargestellt.
IOMMU-Hardware bietet zumindest die Möglichkeit, einem Gerät Lese-/Schreibzugriff auf den physischen Speicher auf Seitenebene zu gewähren und zu entziehen. 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 virtueller Maschinen zu gewährleisten, müssen Speichertransaktionen, die im Auftrag verschiedener Entitäten generiert werden, von der IOMMU unterscheidbar sein, damit der entsprechende Satz von Seitentabellen für die Übersetzung verwendet werden kann.
Darüber hinaus ist die Reduzierung des SoC-spezifischen Codes auf EL2 eine wichtige Strategie, um die allgemeine vertrauenswürdige Rechenbasis (Trusted Computing Base, TCB) von pKVM zu reduzieren. Dies widerspricht der Einbeziehung von IOMMU-Treibern in den Hypervisor. Um dieses Problem zu beheben, ist der Host bei EL1 für zusätzliche IOMMU-Verwaltungsaufgaben zuständig, z. B. für die Energieverwaltung, die Initialisierung und gegebenenfalls die Verwaltung von Unterbrechungen.
Wenn der Host jedoch die Kontrolle über den Gerätestatus hat, werden an die Programmierschnittstelle der IOMMU-Hardware zusätzliche Anforderungen gestellt, um sicherzustellen, dass Berechtigungsprüfungen nicht auf andere Weise umgangen werden können, z. B. nach dem Zurücksetzen des Geräts.
Eine standardmäßige und gut unterstützte IOMMU für Arm-Geräte, die sowohl die Isolation als auch die direkte Zuweisung ermöglicht, ist die SMMU-Architektur (System Memory Management Unit). Diese Architektur ist die empfohlene Referenzlösung.
Arbeitsspeicherinhaberschaft
Beim Booten wird davon ausgegangen, dass der gesamte Nicht-Hypervisor-Arbeitsspeicher dem Host gehört, und wird entsprechend vom Hypervisor verfolgt. Wenn eine pVM erzeugt wird, stellt der Host Arbeitsspeicherseiten bereit, damit sie starten können, und der Hypervisor überträgt die Inhaberschaft dieser Seiten vom Host auf die pVM. Daher implementiert der Hypervisor Zugriffskontrollbeschränkungen in der Seitentabelle der Phase 2 des Hosts, um zu verhindern, dass er noch einmal auf die Seiten zugreift. Dies sorgt für die Vertraulichkeit des Gastes.
Die Kommunikation zwischen dem Host und den Gästen wird durch eine kontrollierte Speicherfreigabe 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 Hoststufe 2 neu zuzuordnen. In ähnlicher Weise wird die Kommunikation des Hosts mit TrustZone durch Speicherfreigabe- und/oder -verleihvorgänge ermöglicht, die alle von pKVM mithilfe der FF-A-Spezifikation (Firmware Framework for Arm) genau überwacht und kontrolliert werden.
Da sich die Speicheranforderungen einer pVM im Laufe der Zeit ändern können, wird ein Hypercall bereitgestellt, mit dem die Inhaberschaft an bestimmten Seiten, die dem Aufrufer gehören, an den Host zurückgegeben werden kann. In der Praxis wird dieser Hypercall mit dem Virtio Balloon-Protokoll verwendet, damit das VMM Arbeitsspeicher von der pVM zurückfordern und das VMM auf kontrollierte Weise über die aufgegebenen Seiten benachrichtigt werden kann.
Der Hypervisor ist dafür verantwortlich, die Inhaberschaft aller Speicherseiten im System zu verfolgen und festzustellen, ob sie freigegeben oder an andere Entitäten geliehen werden. Der größte Teil der Statusverfolgung erfolgt mithilfe von Metadaten, die an die Seitentabellen der Host- und Gästephase 2 angehängt werden. Dabei werden reservierte Bits in den Seitentabelleneinträgen (Page Table Einträgen, PTEs) verwendet, die, wie der Name schon sagt, für die Softwarenutzung reserviert sind.
Der Host muss sicherstellen, dass er nicht versucht, auf Seiten zuzugreifen, die für den Hypervisor unzugänglich gemacht wurden. Ein unzulässiger Hostzugriff führt dazu, dass vom Hypervisor eine synchrone Ausnahme in den Host eingeschleust wird. Dies kann entweder dazu führen, dass die zuständige Userspace-Aufgabe ein SEGV-Signal erhält oder der Hostkernel abstürzt. Um versehentliche Zugriffe zu verhindern, dürfen Seiten, die an Gäste zur Verfügung gestellt werden, nicht für das Austauschen oder Zusammenführen durch den Host-Kernel zugelassen werden.
Bedienung und Timer unterbrechen
Unterbrechungen sind ein wesentlicher Bestandteil der Interaktion eines Gasts mit Geräten und für die Kommunikation zwischen CPUs. Dabei sind Interprozessor-Unterbrechungen der Hauptkommunikationsmechanismus. 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 Emulation des Generic Interrupt Controller Version 3 (GICv3) auf Basis des vorhandenen KVM-Codes. Timer und IPIs werden als Teil dieses nicht vertrauenswürdigen Emulationscodes 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 Regionen mit gemeinsam genutztem Arbeitsspeicher erreicht, eine pro virtueller CPU (vCPU).
Der Unterstützungscode für die Laufzeit des Systemregisters kann so vereinfacht werden, dass nur die Register Trapping (Software Generated Interrupt Register, SGIR) und DIR (Disable Interrupt Register) unterstützt werden. Die Architekturanforderungen, die diese Registrierungen immer an EL2 abfragen, während die anderen Fallen bisher nur zur Behebung von Fehlern hilfreich waren. Alles andere wird über die Hardware abgewickelt.
Auf der MMIO-Seite wird alles auf EL1 emuliert und die gesamte aktuelle Infrastruktur in KVM wiederverwendet. Schließlich wird Warten auf Unterbrechung (WFI) immer an EL1 weitergeleitet, da dies eine der einfachen Planungsprimitive ist, die von KVM verwendet werden.
Timer-Unterstü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 eine GIC-Emulation durchzuführen, müssen MMIO-Traps für weitere Triage an den Host in EL1 zurückgesendet werden. pKVM erfordert Folgendes:
- IPA und Größe des Zugriffs
- Daten bei einem Schreibvorgang
- Endianness der CPU am Überlaufpunkt
Darüber hinaus werden Fallen mit einem Universalregister (GPR) als Quelle/Ziel mithilfe eines abstrakten Pseudoregisters weitergeleitet.
Gastoberflächen
Ein Gast kann über eine Kombination aus Hyperaufrufen und Arbeitsspeicherzugriff auf eingeschlossene Regionen mit einem geschützten Gast kommunizieren. Hypercalls werden gemäß dem SMCCC-Standard bereitgestellt, wobei ein Bereich für eine Anbieterzuweisung durch KVM reserviert ist. Die folgenden Hyperaufrufe sind für pKVM-Gäste von besonderer Bedeutung.
Generische Hypercalls
- PSCI bietet einen Standardmechanismus, mit dem der Gast den Lebenszyklus seiner vCPUs steuern kann, einschließlich Online- und Offlinebereitstellung 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 einen Hardware-Zufallsnummerngenerator (RNG) nicht virtualisieren kann.
pKVM-Hypercalls
- Arbeitsspeicherfreigabe mit dem Host. Der gesamte Gastarbeitsspeicher ist für den Host anfangs nicht zugänglich. Der Hostzugriff ist jedoch für die Kommunikation mit gemeinsam genutztem Speicher und für paravirtualisierte Geräte erforderlich, die auf gemeinsam genutzten Puffern angewiesen sind. Hyperaufrufe zum Freigeben und Aufheben der Freigabe von Seiten für den Host ermöglichen es dem Gast, genau zu entscheiden, welche Teile des Arbeitsspeichers für den Rest von Android ohne Handshake zugänglich gemacht werden.
- Arbeitsspeicherverzicht an den Host. Der gesamte Gastspeicher gehört in der Regel dem Gastspeicher, bis er gelöscht wird. Dieser Status kann für langlebige VMs mit Speicheranforderungen, die sich im Laufe der Zeit ändern, unzureichend sein. Mit dem Hypercall
relinquish
kann ein Gast die Inhaberschaft von Seiten explizit zurück an den Host übertragen, ohne dass dies durch den Gast gekündigt 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 erleichtern, muss pKVM Details zur fehlerhaften Anweisung bereitstellen, z. B. Adresse, Registrierungsparameter und möglicherweise deren Inhalte an den Host. Dies könnte unbeabsichtigt sensible Daten von einem geschützten Gast preisgeben, wenn der Trap nicht erwartet wurde. pKVM behebt dieses Problem, indem pKVM diese Fehler als schwerwiegend an den Host zurückgreift, wenn der Gast nicht zuvor einen Hypercall für den Host-Zugriff als erlaubt hat, um den Host zu identifizieren. 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 geschützten Gästen zugänglich sind, werden mithilfe von 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, der eingeschlossene Speicherzugriffe vom Gast auf die MMIO-Schnittstelle des Virtio-Geräts abfängt und das erwartete Verhalten emuliert. Der MMIO-Zugriff ist relativ teuer, da jeder Zugriff auf das Gerät einen Umlauf zum VMM und zurück erfordert, sodass der größte Teil der tatsächlichen Datenübertragung zwischen dem Gerät und dem Gast über eine Reihe von virtuellen Warteschlangen im Arbeitsspeicher erfolgt. Eine wichtige Annahme von Virtio ist, dass der Host beliebig auf den Gastspeicher zugreifen kann. Diese Annahme zeigt sich im Design der virtqueue, die Hinweise auf Zwischenspeicher im Gast enthalten kann, auf die die Geräteemulation direkt zugreifen soll.
Obwohl die zuvor beschriebenen Hyperaufrufe zur Arbeitsspeicherfreigabe verwendet werden könnten, um Virtio-Datenpuffer vom Gast an den Host zu teilen, muss diese Freigabe seitengenau erfolgen und es könnte am Ende mehr Daten als erforderlich zur Verfügung stehen, wenn der Zwischenspeicher kleiner als die einer Seite ist. Stattdessen ist der Gast so konfiguriert, dass er sowohl die virtuellen Warteschlangen als auch die entsprechenden Datenpuffer aus einem festen Fenster des gemeinsamen Arbeitsspeichers zuweist, wobei Daten nach Bedarf in das und aus dem Fenster kopiert werden (abgesprungen).
Interaktion mit TrustZone
Obwohl Gäste nicht direkt mit TrustZone interagieren können, muss der Host weiterhin SMC-Aufrufe an die sichere Welt senden können. Diese Aufrufe können physisch adressierte Speicherpuffer angeben, die für den Host nicht zugänglich sind. Da die sichere Software den Zugriff auf den Zwischenspeicher im Allgemeinen nicht kennt, könnte ein schädlicher Host diesen Zwischenspeicher verwenden, um einen Confused Deputy-Angriff durchzuführen (ähnlich wie ein DMA-Angriff). Um solche Angriffe zu verhindern, erfasst pKVM alle Host-SMC-Aufrufe an EL2 und fungiert als Proxy zwischen dem Host und dem sicheren Monitor bei EL3.
PSCI-Aufrufe vom Host werden mit minimalen Änderungen an die EL3-Firmware weitergeleitet. Insbesondere wird der Einstiegspunkt für eine CPU, die online geht oder deren Sperrung fortgesetzt wird, so umgeschrieben, dass die Seitentabelle von Phase 2 bei EL2 installiert wird, bevor sie zum Host bei EL1 zurückkehrt. Während des Bootvorgangs wird dieser Schutz von der pKVM erzwungen.
Diese Architektur basiert auf dem SoC, das PSCI unterstützt, vorzugsweise durch die Verwendung einer aktuellen Version von TF-A als EL3-Firmware.
Firmware Framework for Arm (FF-A) standardisiert Interaktionen zwischen der normalen und sicheren Welt, insbesondere bei Vorhandensein eines sicheren Hypervisors. Ein Großteil der Spezifikation definiert einen Mechanismus zur gemeinsamen Nutzung des Arbeitsspeichers für die sichere Welt, der sowohl ein gemeinsames Nachrichtenformat als auch ein klar definiertes Berechtigungsmodell für die zugrunde liegenden Seiten verwendet. pKVM stellt FF-A-Nachrichten bereit, um sicherzustellen, dass der Host nicht versucht, Arbeitsspeicher für die sichere Seite freizugeben, für die er keine ausreichenden Berechtigungen hat.
Diese Architektur basiert auf der Secure World-Software, die das Speicherzugriffsmodell durchsetzt, um sicherzustellen, dass vertrauenswürdige Anwendungen und jede andere in der sicheren Welt ausgeführte Software nur dann auf den Arbeitsspeicher zugreifen können, wenn dieser entweder ausschließlich der sicheren Welt gehört oder explizit über FF-A für ihn freigegeben wurde. Auf einem System mit S-EL2 sollte die Erzwingung des Speicherzugriffsmodells von einem Secure Partition Manager Core (SPMC) wie Hafnium durchgeführt werden, das Seitentabellen der Phase 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 weder ein PSCI-Aufruf noch eine FF-A-definierte Nachricht ist, werden unbehandelte SMCs an EL3 weitergeleitet. Es wird davon ausgegangen, dass die (unbedingt vertrauenswürdige) sichere Firmware unbehandelte SMCs sicher verarbeiten kann, da die Firmware die für die pVM-Isolierung erforderlichen Vorsichtsmaßnahmen versteht.
VM-Monitor
crosvm ist ein Virtual Machine Monitor (VMM), der virtuelle Maschinen über die KVM-Schnittstelle von Linux ausführt. Das Besondere an crosvm ist sein Fokus auf der Sicherheit mit der Programmiersprache Rust und einer Sandbox um virtuelle Geräte zum Schutz des Hostkernels. 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 folgenden Kategorien:
- System ioctls fragen und legen globale Attribute fest, die sich auf das gesamte KVM-Subsystem auswirken, und erstellen pVMs.
- VM ioctls fragt Attribute ab und legt Attribute fest, die virtuelle CPUs (vCPUs) und Geräte erstellen und sich auf eine gesamte pVM auswirken, z. B. das Arbeitsspeicherlayout und die Anzahl der virtuellen CPUs (vCPUs) und Geräte.
- vCPU ioctls fragt Attribute ab und legt Attribute fest, die den Betrieb einer einzelnen virtuellen CPU steuern.
- Mit dem Gerät ioctls werden Attribute abgefragt und festgelegt, die den Betrieb eines einzelnen virtuellen Geräts steuern.
Jeder crosvm-Prozess führt genau eine Instanz einer virtuellen Maschine aus. Dieser Prozess verwendet die Systemioctl KVM_CREATE_VM
, um einen VM-Dateideskriptor zu erstellen, mit dem pVM-ioctls ausgegeben werden können. Ein KVM_CREATE_VCPU
- oder KVM_CREATE_DEVICE
-IOctl auf einer VM-FD erstellt eine vCPU/ein Gerät und gibt einen Dateideskriptor zurück, der auf die neue Ressource verweist. Mit ioctls für eine vCPU- oder Geräte-FD kann das Gerät gesteuert werden, das mit dem ioctl auf einer VM-FD erstellt wurde. Bei vCPUs gehört dies auch zum Ausführen von Gastcode.
crosvm registriert die Dateideskriptoren der VM intern über die Edge-ausgelöste 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 kann. Crosvm verwendet diese bei der pVM-Erstellung, wenn das Flag --protected-vm
übergeben wird, 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 ist die Zuweisung des Arbeitsspeichers der VM und die Verwaltung des Speicherlayouts. crosvm generiert ein Layout mit festem Speicher, wie in der folgenden Tabelle beschrieben.
FDT im Normalmodus | PHYS_MEMORY_END - 0x200000
|
Speicher freigeben | ...
|
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000)
|
Kernel | 0x80080000
|
Bootloader | 0x80200000
|
FDT im BIOS-Modus | 0x80000000
|
Physische Arbeitsspeicherbasis | 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 Gast-pVM-Arbeitsspeicher wird daher der crosvm-Instanz zugeschrieben, die ihn verwaltet. Dies kann dazu führen, dass der Prozess beendet (die VM beendet wird), wenn dem Host nicht mehr kostenloser Arbeitsspeicher zur Verfügung steht. Wenn eine VM beendet wird, wird der Arbeitsspeicher automatisch vom Hypervisor gelöscht und an den Host-Kernel zurückgegeben.
Unter der regulären KVM behält das VMM den Zugriff auf den gesamten Gastarbeitsspeicher bei. Mit pKVM wird die Zuordnung des Gastspeichers zum physischen Adressbereich des Hosts aufgehoben, wenn er dem Gast zur Verfügung gestellt wird. Die einzige Ausnahme ist der Arbeitsspeicher, der explizit vom Gast freigegeben wird, z. B. für Virtio-Geräte.
MMIO-Regionen im Adressbereich des Gasts werden nicht zugeordnet. Der Zugriff auf diese Regionen durch den Gast wird gesperrt und führt zu einem E/A-Ereignis auf der VM-FD. Dieser Mechanismus wird zur Implementierung virtueller Geräte verwendet. Im geschützten Modus muss der Gast mit einem Hypercall bestätigen, dass eine Region seines Adressbereichs für MMIO verwendet wird, um das Risiko versehentlicher Datenlecks zu verringern.
Planung
Jede virtuelle CPU wird durch einen POSIX-Thread dargestellt und vom Linux-Host-Planer 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-Planer berücksichtigt die im Gastkontext aufgewendete Zeit als Zeit, die vom entsprechenden vCPU-Thread verwendet wird. KVM_RUN
gibt zurück, wenn ein Ereignis vorhanden ist, das vom VMM verarbeitet werden muss, z. B. E/A, Ende des Unterbrechungsvorgangs oder angehaltene vCPU. Das VMM verarbeitet das Ereignis und ruft KVM_RUN
noch einmal auf.
Während des KVM_RUN
bleibt der Thread vom Host-Planer auf Abruf verfügbar, mit Ausnahme der Ausführung des EL2-Hypervisor-Codes, der nicht auf Abruf ist. Die Gast-pVM selbst hat keinen Mechanismus zum Steuern dieses Verhaltens.
Da alle vCPU-Threads wie alle anderen Nutzerbereichsaufgaben geplant werden, unterliegen sie allen standardmäßigen QoS-Mechanismen. Insbesondere kann jeder vCPU-Thread an physische CPUs angehängt, in CPUsets platziert, durch Auslastungsbeschränkung erhöht oder begrenzt werden, seine Prioritäts-/Planungsrichtlinie ändern und vieles mehr.
Virtuelle Geräte
crosvm unterstützt eine Reihe von Geräten, darunter die folgenden:
- 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 Real Time Clock (RTC)
- 16550a UART für 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.
Die pvmfw-Binärdatei wird in einer gleichnamigen Flash-Partition gespeichert und mithilfe von OTA aktualisiert.
Gerätestart
Die folgende Abfolge von Schritten wird dem Bootvorgang eines pKVM-fähigen Geräts hinzugefügt:
- Der Android-Bootloader (ABL) lädt pvmfw aus seiner Partition in den Arbeitsspeicher und überprüft das Image.
- Der ABL bezieht seine DICE-Secrets (Device Identifier Composition Engine) (Compound Device Identifiers (CDIs) und DICE-Zertifikatskette) von einer Root of Trust.
- Der ABL leitet die erforderlichen CDIs für pvmfw ab und hängt sie an die pvmfw-Binärdatei an.
- Der ABL fügt dem DT einen
linux,pkvm-guest-firmware-memory
-Knoten der reservierten Speicherregion hinzu, der den Speicherort und die Größe der pvmfw-Binärdatei und die Secrets, die er im vorherigen Schritt abgeleitet hat, beschreibt. - Der ABL-Dienst übergibt die Kontrolle an Linux und Linux initialisiert die pKVM.
- pKVM hebt die Zuordnung des pvmfw-Speicherbereichs von den Seitentabellen der Phase 2 des Hosts auf und schützt sie während der Gerätelaufzeit vor dem Host (und den Gästen).
Nach dem Gerätestart wird Microdroid gemäß den Schritten im Abschnitt Bootsequenz des Microdroid-Dokuments gestartet.
pVM-Start
Beim Erstellen einer pVM-Instanz muss crosvm (oder ein anderes VMM) eine ausreichend große memslot erstellen, um vom Hypervisor mit dem pvmfw-Image gefüllt zu werden. Das VMM ist auch in der Liste der Register eingeschränkt, deren Anfangswert er festlegen kann (x0-x14 für die primäre vCPU, kein Wert für sekundäre vCPUs). Die verbleibenden Register sind reserviert und Teil des ABI „Hypervisor-pvmfw“.
Wenn die pVM ausgeführt wird, übergibt der Hypervisor zuerst die Steuerung der primären 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 bei bekannten Offsets in den Arbeitsspeicher. pvmfw validiert die AVB-Signatur und generiert im Erfolgsfall einen vertrauenswürdigen Gerätebaum aus der empfangenen FDT, löscht deren Secrets aus dem Arbeitsspeicher und verzweigt sich zum Einstiegspunkt der Nutzlast. Wenn einer der Verifizierungsschritte fehlschlägt, gibt die Firmware einen PSCI-SYSTEM_RESET
-Hyperaufruf aus.
Zwischen den Startvorgängen werden Informationen über die pVM-Instanz in einer Partition (virtio-blk-Gerät) gespeichert und mit dem Secret von pvmfw verschlüsselt, um sicherzustellen, dass das Secret nach einem Neustart für die richtige Instanz bereitgestellt wird.