Android bietet eine Referenzimplementierung aller Komponenten, die zum Implementieren des Android Virtualization Framework erforderlich sind. Derzeit ist diese Implementierung auf ARM64 beschränkt. Auf dieser Seite wird die Framework-Architektur erläutert.
Hintergrund
Die Arm-Architektur lässt bis zu vier Ausnahmeebenen zu, wobei Ausnahmeebene 0 (EL0) die am wenigsten privilegierte und Ausnahmeebene 3 (EL3) die höchste ist. Der größte Teil der Android-Codebasis (alle Userspace-Komponenten) läuft auf EL0. Der Rest dessen, was allgemein als „Android“ bezeichnet wird, ist der Linux-Kernel, der auf EL1 läuft.
Die EL2-Schicht ermöglicht die Einführung eines Hypervisors, der das Isolieren von Speicher und Geräten in einzelne pVMs bei EL1/EL0 mit starken Vertraulichkeits- und Integritätsgarantien ermöglicht.
Hypervisor
Die geschützte Kernel-basierte virtuelle Maschine (pKVM) baut auf dem Linux KVM-Hypervisor auf, der um die Möglichkeit erweitert wurde, den Zugriff auf die Payloads zu beschränken, die in virtuellen Gastmaschinen ausgeführt werden, die zum Zeitpunkt der Erstellung als „geschützt“ gekennzeichnet waren.
KVM/arm64 unterstützt je nach Verfügbarkeit bestimmter CPU-Funktionen verschiedene Ausführungsmodi, nämlich die Virtualization Host Extensions (VHE) (ARMv8.1 und höher). In einem dieser Modi, allgemein als Non-VHE-Modus bekannt, wird der Hypervisor-Code während des Bootens aus dem Kernel-Image herausgelöst und auf EL2 installiert, während der Kernel selbst auf EL1 läuft. Obwohl Teil der Linux-Codebasis, ist die EL2-Komponente von KVM eine kleine Komponente, die für die Umschaltung zwischen mehreren EL1s zuständig ist und vollständig vom Kernel des Hosts gesteuert wird. Die Hypervisor-Komponente wird mit Linux kompiliert, befindet sich jedoch in einem separaten, dedizierten Speicherabschnitt des vmlinux
-Images. pKVM nutzt dieses Design, indem es den Hypervisor-Code um neue Funktionen erweitert, die es ermöglichen, den Android-Host-Kernel und den Benutzerbereich einzuschränken und den Host-Zugriff auf Gastspeicher und Hypervisor zu beschränken.
Bootvorgang
Das pKVM-Boot-Verfahren ist in Abbildung 1 dargestellt. Der erste Schritt für den Bootloader besteht darin, einen pKVM-fähigen Linux-Kernel bei EL2 einzugeben. Während des frühen Bootens erkennt der Kernel, dass er auf EL2 läuft, entzieht sich EL1 und lässt pKVM zurück. Von diesem Punkt an bootet der Linux-Kernel normal und lädt alle erforderlichen Gerätetreiber, bis er den Benutzerbereich erreicht. Diese Schritte erfolgen unter der Kontrolle von pKVM.
Die Boot-Prozedur vertraut darauf, dass der Bootloader die Integrität des Kernel-Images nur während des frühen Bootens aufrechterhält. Wenn der Kernel entprivilegiert wird, wird er vom Hypervisor nicht mehr als vertrauenswürdig betrachtet, der dann dafür verantwortlich ist, sich selbst zu schützen, selbst wenn der Kernel kompromittiert wird.
Das Vorhandensein des Android-Kernels und des Hypervisors im selben Binärbild ermöglicht eine sehr eng gekoppelte Kommunikationsschnittstelle zwischen ihnen. Diese enge Kopplung garantiert atomare Updates der beiden Komponenten, wodurch die Notwendigkeit entfällt, die Schnittstelle zwischen ihnen stabil zu halten, und bietet ein hohes Maß an 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.
Darüber hinaus ermöglicht die Einführung von GKI im Android-Ökosystem automatisch, dass der pKVM-Hypervisor auf Android-Geräten in derselben Binärdatei wie der Kernel bereitgestellt wird.
CPU-Speicherzugriffsschutz
Die Arm-Architektur spezifiziert eine Speicherverwaltungseinheit (MMU), die in zwei unabhängige Stufen aufgeteilt ist, die beide verwendet werden können, um die Adressübersetzung und die Zugriffskontrolle auf verschiedene Teile des Speichers zu implementieren. Die MMU der Stufe 1 wird von EL1 gesteuert und ermöglicht eine erste Ebene der Adressübersetzung. 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 Adressumsetzung auf die Ausgangsadresse der MMU der Stufe 1, was zu einer physikalischen Adresse (PA) führt. Die Stufe-2-Übersetzung kann von Hypervisoren verwendet werden, um Speicherzugriffe von allen Gast-VMs zu steuern und zu übersetzen. Wie in Abbildung 2 gezeigt, wird die Ausgangsadresse der Stufe 1, wenn beide Übersetzungsstufen aktiviert sind, als physikalische Zwischenadresse (IPA) bezeichnet. Hinweis: Die virtuelle Adresse (VA) wird in eine IPA und dann in eine PA übersetzt.
In der Vergangenheit wurde KVM mit aktivierter Stufe-2-Übersetzung ausgeführt, während Gäste ausgeführt wurden, und mit deaktivierter Stufe 2, während der Host-Linux-Kernel ausgeführt wurde. Diese Architektur ermöglicht, dass Speicherzugriffe von der MMU der Stufe 1 des Hosts durch die MMU der Stufe 2 geleitet werden, wodurch ein uneingeschränkter Zugriff von den Speicherseiten des Hosts auf die Gastspeicherseiten ermöglicht wird. Auf der anderen Seite ermöglicht pKVM Schutz der Stufe 2 sogar im Host-Kontext und übergibt dem Hypervisor die Verantwortung für den Schutz von Gast-Speicherseiten anstelle des Hosts.
KVM nutzt die Adressübersetzung in Stufe 2 vollständig aus, um komplexe IPA/PA-Mappings für Gäste zu implementieren, wodurch trotz physischer Fragmentierung die Illusion eines zusammenhängenden Speichers für Gäste entsteht. Die Verwendung der MMU der Stufe 2 für den Host ist jedoch nur auf die Zugriffssteuerung beschränkt. Die Hoststufe 2 ist identitätsabgebildet, was sicherstellt, dass zusammenhängender Speicher im Host-IPA-Raum zusammenhängend im PA-Raum ist. Diese Architektur ermöglicht die Verwendung großer Abbildungen in der Seitentabelle und reduziert folglich den Druck auf den Übersetzungs-Lookaside-Puffer (TLB). Da eine Identitätszuordnung von PA indiziert werden kann, wird die Hoststufe 2 auch verwendet, um den Seitenbesitz direkt in der Seitentabelle zu verfolgen.
Schutz für direkten Speicherzugriff (DMA).
Wie zuvor beschrieben, ist das Aufheben der Zuordnung von Gastseiten vom Linux-Host in den CPU-Seitentabellen ein notwendiger, aber unzureichender Schritt zum Schutz des Gastspeichers. pKVM muss auch vor Speicherzugriffen durch DMA-fähige Geräte unter der Kontrolle des Host-Kernels und der Möglichkeit eines DMA-Angriffs durch einen böswilligen Host schützen. 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.
Die IOMMU-Hardware bietet zumindest die Möglichkeit, Lese-/Schreibzugriff für ein Gerät auf den physischen Speicher mit Seitengranularität zu gewähren und zu widerrufen. Diese IOMMU-Hardware schränkt jedoch die Verwendung von Geräten in pVMs ein, da sie von einer identitätszugeordneten Stufe 2 ausgehen.
Um eine Isolierung zwischen virtuellen Maschinen sicherzustellen, müssen Speichertransaktionen, die für verschiedene Entitäten generiert werden, durch die IOMMU unterscheidbar sein, damit der geeignete Satz von Seitentabellen für die Übersetzung verwendet werden kann.
Darüber hinaus ist die Reduzierung der Menge an SoC-spezifischem Code bei EL2 eine Schlüsselstrategie zur Reduzierung der gesamten Trusted Computing Base (TCB) von pKVM und widerspricht der Aufnahme von IOMMU-Treibern in den Hypervisor. Um dieses Problem zu mindern, ist der Host bei EL1 für zusätzliche IOMMU-Verwaltungsaufgaben verantwortlich, wie z. B. Energieverwaltung, Initialisierung und gegebenenfalls Interrupt-Handhabung.
Wenn dem Host jedoch die Kontrolle über den Gerätestatus übertragen wird, werden zusätzliche Anforderungen an die Programmierschnittstelle der IOMMU-Hardware gestellt, um sicherzustellen, dass Berechtigungsprüfungen nicht auf andere Weise umgangen werden können, beispielsweise nach einem Geräte-Reset.
Eine standardmäßige und gut unterstützte IOMMU für ARM-Geräte, die sowohl Isolierung als auch direkte Zuweisung ermöglicht, ist die Architektur der ARM System Memory Management Unit (SMMU). Diese Architektur ist die empfohlene Referenzlösung.
Speicherbesitz
Beim Booten wird davon ausgegangen, dass der gesamte Nicht-Hypervisor-Speicher dem Host gehört, und wird als solcher vom Hypervisor verfolgt. Wenn eine pVM erzeugt wird, spendet der Host Speicherseiten, damit sie booten kann, und der Hypervisor überträgt den Besitz dieser Seiten vom Host auf die pVM. Daher richtet der Hypervisor Zugriffskontrollbeschränkungen in der Seitentabelle der Stufe 2 des Hosts ein, um zu verhindern, dass er erneut auf die Seiten zugreift, und sorgt so für Vertraulichkeit für den Gast.
Die Kommunikation zwischen dem Host und den Gästen wird durch eine kontrollierte gemeinsame Nutzung des Speichers zwischen ihnen ermöglicht. Gäste können einige ihrer Seiten mithilfe eines Hypercalls wieder mit dem Host teilen, der den Hypervisor anweist, diese Seiten in der Seitentabelle der Host-Stufe 2 neu zuzuordnen. In ähnlicher Weise wird die Kommunikation des Hosts mit TrustZone durch gemeinsame Arbeitsspeicher- und/oder Leihoperationen ermöglicht, die alle von pKVM unter Verwendung der Firmware Framework for Arm (FF-A)-Spezifikation genau überwacht und gesteuert werden.
Der Hypervisor ist dafür verantwortlich, den Besitz aller Speicherseiten im System zu verfolgen und festzustellen, ob sie gemeinsam genutzt oder an andere Entitäten verliehen werden. Der größte Teil dieser Zustandsverfolgung erfolgt mithilfe von Metadaten, die an die Seitentabellen der Stufe 2 der Hosts und Gäste angehängt sind, wobei reservierte Bits in den Seitentabelleneinträgen (PTEs) verwendet werden, die, wie der Name schon sagt, für die Verwendung durch Software reserviert sind.
Der Host muss sicherstellen, dass er nicht versucht, auf Seiten zuzugreifen, die vom Hypervisor unzugänglich gemacht wurden. Ein illegaler Host-Zugriff führt dazu, dass vom Hypervisor eine synchrone Ausnahme in den Host eingeschleust wird, was entweder dazu führen kann, dass die zuständige Userspace-Task ein SEGV-Signal erhält, oder der Host-Kernel abstürzt. Um versehentliche Zugriffe zu verhindern, werden an Gäste gespendete Seiten vom Host-Kernel für das Austauschen oder Zusammenführen ungeeignet gemacht.
Interrupt-Behandlung und Timer
Interrupts sind ein wesentlicher Bestandteil der Art und Weise, wie ein Gast mit Geräten interagiert und für die Kommunikation zwischen CPUs, wobei Interprozessor-Interrupts (IPIs) der Hauptkommunikationsmechanismus sind. Das KVM-Modell delegiert die gesamte virtuelle Unterbrechungsverwaltung an den Host in EL1, der sich zu diesem Zweck als nicht vertrauenswürdiger Teil des Hypervisors verhält.
pKVM bietet eine vollständige Generic Interrupt Controller Version 3 (GICv3)-Emulation basierend auf dem bestehenden KVM-Code. Timer und IPIs werden als Teil dieses nicht vertrauenswürdigen Emulationscodes behandelt.
GICv3-Unterstützung
Die Schnittstelle zwischen EL1 und EL2 muss sicherstellen, dass der vollständige Interrupt-Status für den EL1-Host sichtbar ist, einschließlich Kopien der Hypervisor-Register, die sich auf Interrupts beziehen. Diese Sichtbarkeit wird in der Regel mithilfe gemeinsam genutzter Speicherregionen erreicht, einer pro virtueller CPU (vCPU).
Der Systemregister-Laufzeit-Unterstützungscode kann vereinfacht werden, um nur das Register-Trapping des Software Generated Interrupt Register (SGIR) und des Deactivate Interrupt Register (DIR) zu unterstützen. Die Architektur schreibt vor, dass diese Register immer auf EL2 trappen, während die anderen Traps bisher nur nützlich waren, um Errata abzumildern. Alles andere wird in Hardware gehandhabt.
Auf der MMIO-Seite wird alles bei EL1 emuliert, wobei die gesamte aktuelle Infrastruktur in KVM wiederverwendet wird. Schließlich wird Wait for Interrupt (WFI) immer an EL1 weitergeleitet, da dies eines der grundlegenden Scheduling-Primitive ist, die KVM verwendet.
Timer-Unterstützung
Der Komparatorwert für den virtuellen Timer muss EL1 auf jeder einfangenden WFI offengelegt werden, damit EL1 Timer-Interrupts injizieren kann, während die vCPU blockiert ist. Der physikalische Timer wird vollständig emuliert und alle Traps werden an EL1 weitergeleitet.
MMIO-Handhabung
Um mit dem Virtual Machine Monitor (VMM) zu kommunizieren und eine GIC-Emulation durchzuführen, müssen MMIO-Traps zur weiteren Prüfung an den Host in EL1 zurückgesendet werden. pKVM erfordert Folgendes:
- IPA und Größe des Zugriffs
- Daten im Falle eines Schreibvorgangs
- Endianness der CPU am Trapping-Punkt
Zusätzlich werden Traps mit einem Mehrzweckregister (GPR) als Quelle/Ziel unter Verwendung eines abstrakten Übertragungspseudoregisters weitergeleitet.
Gastschnittstellen
Ein Gast kann mit einem geschützten Gast kommunizieren, indem er eine Kombination aus Hypercalls und Speicherzugriff auf eingeschlossene Regionen verwendet. Hypercalls werden gemäß dem SMCCC-Standard bereitgestellt , wobei ein Bereich für eine Anbieterzuweisung durch KVM reserviert ist. Die folgenden Hypercalls sind für pKVM-Gäste von besonderer Bedeutung.
Generische Hypercalls
- PSCI bietet einen Standardmechanismus für den Gast, um den Lebenszyklus seiner vCPUs zu steuern, einschließlich Online, Offline und Herunterfahren des Systems.
- TRNG bietet einen Standardmechanismus für den Gast, um Entropie vom pKVM anzufordern, der den Anruf an EL3 weiterleitet. Dieser Mechanismus ist besonders nützlich, wenn dem Host nicht vertraut werden kann, dass er einen Hardware-Zufallszahlengenerator (RNG) virtualisiert.
pKVM-Hypercalls
- Speicherfreigabe mit dem Host. Der gesamte Gastspeicher ist zunächst für den Host nicht zugänglich, aber der Hostzugriff ist für die Kommunikation über den gemeinsam genutzten Speicher und für paravirtualisierte Geräte erforderlich, die auf gemeinsam genutzte Puffer angewiesen sind. Hypercalls zum Teilen und Entteilen von Seiten mit dem Host ermöglichen es dem Gast, genau zu entscheiden, welche Teile des Speichers dem Rest von Android zugänglich gemacht werden, ohne dass ein Handshake erforderlich ist.
- Speicherzugriffs-Trapping zum Host. Wenn ein KVM-Gast auf eine Adresse zugreift, die keinem gültigen Speicherbereich entspricht, verlässt der vCPU-Thread traditionell den Host und der Zugriff wird normalerweise für MMIO verwendet und vom VMM im Benutzerbereich emuliert. Um diese Handhabung zu erleichtern, muss pKVM Details über die fehlerhafte Anweisung wie ihre Adresse, Registerparameter und möglicherweise deren Inhalt an den Host zurücksenden, was unbeabsichtigt vertrauliche Daten von einem geschützten Gast preisgeben könnte, wenn die Falle nicht vorhergesehen wurde. pKVM löst dieses Problem, indem es diese Fehler als schwerwiegend behandelt, es sei denn, der Gast hat zuvor einen Hypercall ausgegeben, um den fehlerhaften IPA-Bereich als einen zu identifizieren, für den Zugriffe auf den Host zurückfallen dürfen. Diese Lösung wird als MMIO-Wächter bezeichnet.
Virtuelles E/A-Gerät (virtio)
Virtio ist ein beliebter, portabler und ausgereifter Standard für die Implementierung und Interaktion mit paravirtualisierten Geräten. Die Mehrzahl der Geräte, die geschützten Gästen ausgesetzt 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 typischerweise im Benutzerbereich des Hosts durch den VMM implementiert, der abgefangene 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 Roundtrip zum VMM und zurück erfordert, sodass der größte Teil der eigentlichen Datenübertragung zwischen dem Gerät und dem Gast unter Verwendung einer Reihe von Virtqueues im Speicher erfolgt. Eine Schlüsselannahme von virtio ist, dass der Host willkürlich auf Gastspeicher zugreifen kann. Diese Annahme zeigt sich im Design der Virtqueue, die möglicherweise Zeiger auf Puffer im Gast enthält, auf die die Geräteemulation direkt zugreifen soll.
Obwohl die zuvor beschriebenen Memory-Sharing-Hypercalls verwendet werden könnten, um Virtio-Datenpuffer vom Gast zum Host zu teilen, erfolgt diese gemeinsame Nutzung notwendigerweise mit Seitengranularität und könnte am Ende mehr Daten als erforderlich offenlegen, wenn die Puffergröße kleiner als die einer Seite ist . Stattdessen ist der Gast so konfiguriert, dass er sowohl die Virtqueues als auch ihre entsprechenden Datenpuffer aus einem festen Fenster des gemeinsam genutzten Speichers zuweist, wobei Daten je nach Bedarf in das und aus dem Fenster kopiert (gesprungen) werden.
Interaktion mit TrustZone
Obwohl Gäste nicht direkt mit TrustZone interagieren können, muss der Host dennoch in der Lage sein, SMC-Aufrufe in die sichere Welt zu senden. Diese Aufrufe können physikalisch adressierte Speicherpuffer spezifizieren, auf die der Host nicht zugreifen kann. Da die sichere Software im Allgemeinen die Zugänglichkeit des Puffers nicht kennt, könnte ein bösartiger Host diesen Puffer verwenden, um einen verwirrten Stellvertreterangriff durchzuführen (analog zu einem DMA-Angriff). Um solche Angriffe zu verhindern, fängt pKVM alle Host-SMC-Aufrufe an EL2 ab und fungiert als Proxy zwischen dem Host und dem sicheren Monitor an EL3.
PSCI-Aufrufe vom Host werden mit minimalen Modifikationen an die EL3-Firmware weitergeleitet. Insbesondere wird der Eintrittspunkt für eine CPU, die online geht oder aus dem Suspend wieder aufgenommen wird, neu geschrieben, so dass die Seitentabelle der Stufe 2 bei EL2 installiert wird, bevor sie bei EL1 zum Host zurückkehrt. Während des Bootens wird dieser Schutz durch pKVM erzwungen.
Diese Architektur stützt sich darauf, dass der SoC PSCI unterstützt, vorzugsweise durch die Verwendung einer aktuellen Version von TF-A als seine EL3-Firmware.
Das Firmware Framework for Arm (FF-A) standardisiert die Interaktionen zwischen der normalen und der sicheren Welt, insbesondere in Gegenwart eines sicheren Hypervisors. Ein Großteil der Spezifikation definiert einen Mechanismus zur gemeinsamen Nutzung von Speicher mit der sicheren Welt, wobei sowohl ein gemeinsames Nachrichtenformat als auch ein gut definiertes Berechtigungsmodell für die zugrunde liegenden Seiten verwendet werden. pKVM leitet FF-A-Nachrichten weiter, um sicherzustellen, dass der Host nicht versucht, Arbeitsspeicher mit der sicheren Seite zu teilen, für die er nicht über ausreichende Berechtigungen verfügt.
Diese Architektur stützt sich auf die Secure-World-Software, die das Speicherzugriffsmodell erzwingt, um sicherzustellen, dass vertrauenswürdige Apps und jede andere Software, die in der sicheren Welt ausgeführt wird, nur dann auf den Speicher zugreifen können, wenn sie entweder ausschließlich im Besitz der sicheren Welt sind oder explizit mit FF für sie freigegeben wurden -EIN. Auf einem System mit S-EL2 sollte die Durchsetzung des Speicherzugriffsmodells von einem Secure Partition Manager Core (SPMC) wie Hafnium durchgeführt werden, der Seitentabellen der Stufe 2 für die sichere Welt verwaltet. Auf einem System ohne S-EL2 kann das TEE stattdessen ein Speicherzugriffsmodell durch seine Seitentabellen der Stufe 1 erzwingen.
Wenn der SMC-Aufruf an EL2 kein PSCI-Aufruf oder keine FF-A-definierte Nachricht ist, werden nicht bearbeitete SMCs an EL3 weitergeleitet. Die Annahme ist, dass die (notwendigerweise vertrauenswürdige) sichere Firmware unbehandelte SMCs sicher handhaben kann, da die Firmware die Vorsichtsmaßnahmen versteht, die zum Aufrechterhalten der pVM-Isolation erforderlich sind.
Monitor für virtuelle Maschinen
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 mit der Verwendung der Programmiersprache Rust und einer Sandbox um virtuelle Geräte, um den Host-Kernel zu schützen.
Dateideskriptoren und ioctls
KVM stellt das /dev/kvm
dem Userspace mit ioctls zur Verfügung, die die KVM-API bilden. Die ioctls gehören zu den folgenden Kategorien:
- System ioctls fragen globale Attribute ab und setzen diese, die das gesamte KVM-Subsystem betreffen, und erstellen pVMs.
- VM-ioctls fragen und setzen Attribute, die virtuelle CPUs (vCPUs) und Geräte erstellen und sich auf eine gesamte pVM auswirken, wie z. B. das Speicherlayout und die Anzahl virtueller CPUs (vCPUs) und Geräte.
- vCPU-ioctls fragen Attribute ab und legen sie fest, die den Betrieb einer einzelnen virtuellen CPU steuern.
- Geräte-ioctls fragen Attribute ab und setzen sie, die den Betrieb eines einzelnen virtuellen Geräts steuern.
Jeder crosvm-Prozess führt genau eine Instanz einer virtuellen Maschine aus. Dieser Prozess verwendet das KVM_CREATE_VM
-System-ioctl, um einen VM-Dateideskriptor zu erstellen, der zum Ausgeben von pVM-ioctls verwendet werden kann. Ein KVM_CREATE_VCPU
oder KVM_CREATE_DEVICE
ioctl auf einem VM-FD erstellt eine vCPU/ein Gerät und gibt einen Dateideskriptor zurück, der auf die neue Ressource zeigt. ioctls auf einer vCPU oder einem Geräte-FD kann verwendet werden, um das Gerät zu steuern, das mit dem ioctl auf einem VM-FD erstellt wurde. Für vCPUs beinhaltet dies die wichtige Aufgabe, Gastcode auszuführen.
Intern registriert crosvm die Dateideskriptoren der VM beim Kernel unter Verwendung der Edge-getriggerten epoll
-Schnittstelle. Der Kernel benachrichtigt dann crosvm, wenn in einem der Dateideskriptoren ein neues Ereignis ansteht.
pKVM fügt eine neue Funktion hinzu, KVM_CAP_ARM_PROTECTED_VM
, die verwendet werden kann, um Informationen über die pVM-Umgebung abzurufen und den geschützten Modus für eine VM einzurichten. crosvm verwendet dies während der pVM-Erstellung, wenn das --protected-vm
übergeben wird, um die entsprechende Menge an Speicher für die pVM-Firmware abzufragen und zu reservieren und dann den geschützten Modus zu aktivieren.
Speicherzuweisung
Eine der Hauptaufgaben eines VMM ist die Zuweisung des Arbeitsspeichers der VM und die Verwaltung des Arbeitsspeicherlayouts. crosvm generiert ein festes Speicherlayout, das in der folgenden Tabelle lose beschrieben ist.
FDT im Normalmodus | PHYS_MEMORY_END - 0x200000 |
Freiraum | ... |
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000) |
Kernel | 0x80080000 |
Bootloader | 0x80200000 |
FDT im BIOS-Modus | 0x80000000 |
Physische Speicherbasis | 0x80000000 |
pVM-Firmware | 0x7FE00000 |
Gerätespeicher | 0x10000 - 0x40000000 |
Physischer Speicher wird mit mmap
zugewiesen und der Speicher wird der VM gespendet, um ihre Speicherregionen, genannt memslots , mit dem KVM_SET_USER_MEMORY_REGION
KVM_SET_USER_MEMORY_REGION zu füllen. Der gesamte Gast-pVM-Speicher wird daher der crosvm-Instanz zugeschrieben, die ihn verwaltet, und kann dazu führen, dass der Prozess beendet (die VM beendet) wird, wenn dem Host der freie Speicher ausgeht. Wenn eine VM gestoppt wird, wird der Speicher automatisch vom Hypervisor gelöscht und an den Host-Kernel zurückgegeben.
Unter regulärem KVM behält der VMM den Zugriff auf den gesamten Gastspeicher. Mit pKVM wird Gastarbeitsspeicher vom physischen Adressraum des Hosts getrennt, wenn er an den Gast gespendet wird. Die einzige Ausnahme ist Speicher, der ausdrücklich vom Gast freigegeben wird, z. B. für Virtio-Geräte.
MMIO-Regionen im Adressraum des Gasts bleiben nicht zugeordnet. Der Zugriff auf diese Regionen durch den Gast wird abgefangen und führt zu einem E/A-Ereignis auf dem VM FD. Dieser Mechanismus wird verwendet, um virtuelle Geräte zu implementieren. Im geschützten Modus muss der Gast bestätigen, dass ein Bereich seines Adressraums für MMIO mithilfe eines Hyperaufrufs verwendet wird, um das Risiko eines versehentlichen Informationsverlusts zu verringern.
Planung
Jede virtuelle CPU wird durch einen POSIX-Thread dargestellt und vom Host-Linux-Scheduler geplant. Der Thread ruft das KVM_RUN
ioctl auf dem vCPU-FD auf, was dazu führt, dass der Hypervisor zum Gast-vCPU-Kontext wechselt. Der Host-Scheduler berücksichtigt die in einem Gastkontext verbrachte Zeit als Zeit, die vom entsprechenden vCPU-Thread verwendet wird. KVM_RUN
gibt zurück, wenn ein Ereignis vorliegt, das vom VMM verarbeitet werden muss, z. B. E/A, Unterbrechungsende oder Anhalten der vCPU. Der VMM behandelt das Ereignis und ruft erneut KVM_RUN
auf.
Während KVM_RUN
bleibt der Thread vom Host-Scheduler präemptiv, mit Ausnahme der Ausführung des EL2-Hypervisor-Codes, der nicht präemptiv ist. 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 physischen CPUs zugeordnet, in CPU-Sätzen platziert, mithilfe von Nutzungsbeschränkungen verstärkt oder begrenzt werden, seine Prioritäts-/Planungsrichtlinie geändert werden und vieles mehr.
Virtuelle Geräte
crosvm unterstützt eine Reihe von Geräten, darunter die folgenden:
- virtio-blk für zusammengesetzte Disk-Images, schreibgeschützt oder schreibgeschützt
- vhost-vsock für die Kommunikation mit dem Host
- virtio-pci als virtio-Transport
- pl030 Echtzeituhr (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 sicheren Bootvorgang zu starten und das eindeutige Geheimnis der pVM abzuleiten. pvmfw ist nicht auf die Verwendung mit einem bestimmten Betriebssystem wie Microdroid beschränkt , solange das Betriebssystem von crosvm unterstützt wird und ordnungsgemäß signiert wurde.
Die pvmfw-Binärdatei wird in einer gleichnamigen Flash-Partition gespeichert und über OTA aktualisiert.
Booten des Geräts
Die folgende Abfolge von Schritten wird zum Startvorgang eines pKVM-fähigen Geräts hinzugefügt:
- Der Android Bootloader (ABL) lädt pvmfw von seiner Partition in den Speicher und überprüft das Image.
- Die ABL erhält ihre Device Identifier Composition Engine (DICE)-Geheimnisse (Compound Device Identifiers (CDIs) und Boot Certificate Chain (BCC)) von einem Root of Trust.
- Die ABL führt eine Messung und DICE-Ableitung von pvmfw-Geheimnissen (CDIs) durch und hängt sie an die pvmfw-Binärdatei an.
- Die ABL fügt dem DT einen
linux,pkvm-guest-firmware-memory
reservierten Speicherbereichsknoten hinzu, der den Speicherort und die Größe der pvmfw-Binärdatei und die Geheimnisse beschreibt, die sie im vorherigen Schritt abgeleitet hat. - Die ABL übergibt die Kontrolle an Linux und Linux initialisiert pKVM.
- pKVM hebt die Zuordnung der pvmfw-Speicherregion von den Stage-2-Seitentabellen des Hosts auf und schützt sie während der gesamten Betriebszeit des Geräts vor dem Host (und Gästen).
Nach dem Booten des Geräts wird Microdroid gemäß den Schritten im Abschnitt Boot-Sequenz des Microdroid- Dokuments gebootet.
pVM-Boot
Beim Erstellen einer pVM muss crosvm (oder ein anderer VMM) einen ausreichend großen Memslot erstellen, der vom Hypervisor mit dem pvmfw-Image gefüllt wird. Der VMM ist auch in der Liste der Register eingeschränkt, deren Anfangswert er festlegen kann (x0–x14 für die primäre vCPU, keine für sekundäre vCPUs). Die restlichen Register sind reserviert und Teil der hypervisor-pvmfw ABI.
Wenn die pVM ausgeführt wird, übergibt der Hypervisor zunächst die Kontrolle über die primäre vCPU an pvmfw. Die Firmware erwartet, dass crosvm einen AVB-signierten Kernel, der ein Bootloader oder ein beliebiges anderes Image sein kann, und ein unsigniertes FDT an bekannten Offsets in den Speicher geladen hat. pvmfw validiert die AVB-Signatur und generiert bei Erfolg einen vertrauenswürdigen Gerätebaum aus der empfangenen FDT, löscht seine Geheimnisse aus dem Speicher und verzweigt zum Einstiegspunkt der Nutzdaten. Wenn einer der Überprüfungsschritte 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 Geheimnis von pvmfw verschlüsselt, um sicherzustellen, dass das Geheimnis nach einem Neustart der richtigen Instanz bereitgestellt wird.