Architettura AVF

Android fornisce un'implementazione di riferimento di tutti i componenti necessari per implementare il Framework di virtualizzazione di Android. Al momento, questa implementazione è limitata ad ARM64. Questa pagina illustra l'architettura del framework.

Sfondo

L'architettura Arm consente fino a quattro livelli di eccezione, con il livello di eccezione 0 (EL0) come il meno privilegiato e il livello di eccezione 3 (EL3) come il più privilegiato. La maggior parte del codice di base di Android (tutti i componenti dello spazio utente) viene eseguita in EL0. Il resto di ciò che viene comunemente chiamato "Android" è il kernel Linux, che viene eseguito in EL1.

Il livello EL2 consente l'introduzione di un hypervisor che consente di isolare la memoria e i dispositivi in singole pVM a livello EL1/EL0, con garanzie elevate di riservatezza e integrità.

Hypervisor

La macchina virtuale basata su kernel protetta (pKVM) è basata sull'hypervisor KVM di Linux, che è stato esteso con la possibilità di limitare l'accesso ai payload in esecuzione nelle macchine virtuali guest contrassegnate come "protette" al momento della creazione.

KVM/arm64 supporta diverse modalità di esecuzione a seconda della disponibilità di determinate funzionalità della CPU, ovvero le estensioni host di virtualizzazione (VHE) (ARMv8.1 e versioni successive). In una di queste modalità, nota come modalità non VHE, il codice dell'hypervisor viene separato dall'immagine del kernel durante l'avvio e installato in EL2, mentre il kernel stesso viene eseguito in EL1. Sebbene faccia parte del codebase di Linux, il componente EL2 di KVM è un piccolo componente responsabile del passaggio tra più EL1. Il componente dell'hypervisor viene compilato con Linux, ma risiede in una sezione di memoria dedicata separata dell'immagine vmlinux. pKVM sfrutta questo design estendendo il codice dell'hypervisor con nuove funzionalità che consentono di applicare restrizioni al kernel e allo spazio utente dell'host Android e di limitare l'accesso dell'host alla memoria guest e all'hypervisor.

Moduli del fornitore di pKVM

Un modulo del fornitore pKVM è un modulo specifico per l'hardware contenente funzionalità specifiche del dispositivo, come i driver IOMMU (Input-Output Memory Management Unit). Questi moduli ti consentono di eseguire il porting delle funzionalità di sicurezza che richiedono l'accesso a livello di eccezione 2 (EL2) alla pKVM.

Per scoprire come implementare e caricare un modulo del fornitore pKVM, consulta Implementare un modulo del fornitore pKVM.

Procedura di avvio

La figura seguente mostra la procedura di avvio del pKVM:

Procedura di avvio del pKVM

Figura 1. Procedura di avvio della pKVM

  1. Il bootloader entra nel kernel generico a EL2.
  2. Il kernel generico rileva che è in esecuzione in EL2 e si deprivilegia in EL1, mentre pKVM e i relativi moduli continuano a funzionare in EL2. Inoltre, al momento vengono caricati i moduli dei fornitori di pKVM.
  3. Il kernel generico procede all'avvio normalmente, caricando tutti i driver di dispositivo necessari fino a raggiungere lo spazio utente. A questo punto, la pKVM è in funzione e gestisce le tabelle di pagine della fase 2.

La procedura di avvio si basa sull'affidabilità del bootloader per mantenere l'integrità dell'immagine del kernel solo durante la fase iniziale dell'avvio. Quando il kernel viene deprivato dei privilegi, non è più considerato attendibile dall'hypervisor, che è quindi responsabile della propria protezione anche se il kernel è compromesso.

Avere il kernel di Android e l'hypervisor nella stessa immagine binaria consente di avere un'interfaccia di comunicazione molto stretta tra i due. Questo abbinamento stretto garantisce aggiornamenti atomici dei due componenti, il che evita di dover mantenere stabile l'interfaccia tra di loro e offre una grande flessibilità senza compromettere la manutenibilità a lungo termine. Il legame stretto consente inoltre di ottimizzare le prestazioni quando entrambi i componenti possono collaborare senza influire sulle garanzie di sicurezza fornite dall'hypervisor.

Inoltre, l'adozione di GKI nell'ecosistema Android consente automaticamente di eseguire il deployment dell'hypervisor pKVM sui dispositivi Android nello stesso binario del kernel.

Protezione dell'accesso alla memoria della CPU

L'architettura Arm specifica un'unità di gestione della memoria (MMU) suddivisa in due fasi indipendenti, entrambe utilizzabili per implementare la traduzione degli indirizzi e il controllo dell'accesso a diverse parti della memoria. L'MMU di primo livello è controllata da EL1 e consente un primo livello di traduzione degli indirizzi. L'MMU di primo livello viene utilizzata da Linux per gestire lo spazio indirizzi virtuale fornito a ogni processo dello spazio utente e al proprio spazio indirizzi virtuale.

L'MMU di secondo livello è controllata da EL2 e consente l'applicazione di una seconda traduzione di indirizzo all'indirizzo di uscita dell'MMU di primo livello, generando un indirizzo fisico (PA). La traduzione di secondo livello può essere utilizzata dagli hypervisor per controllare e tradurre gli accessi alla memoria da tutte le VM guest. Come mostrato nella figura 2, quando sono abilitate entrambe le fasi di traduzione, l'indirizzo di output della fase 1 è chiamato indirizzo fisico intermedio (IPA) Nota: l'indirizzo virtuale (VA) viene tradotto in un IPA e poi in un PA.

Protezione dell'accesso alla memoria della CPU

Figura 2. Protezione dell'accesso alla memoria della CPU

In passato, KVM veniva eseguito con la traduzione di fase 2 abilitata durante l'esecuzione degli ospiti e con la fase 2 disattivata durante l'esecuzione del kernel Linux host. Questa architettura consente agli accessi alla memoria dall'MMU di primo livello dell'host di passare tramite l'MMU di secondo livello, consentendo così l'accesso senza restrizioni dall'host alle pagine di memoria guest. D'altra parte, la pKVM abilita la protezione di Livello 2 anche nel contesto dell'host e incarica l'hypervisor di proteggere le pagine di memoria guest anziché l'host.

KVM sfrutta appieno la traduzione degli indirizzi nella fase 2 per implementare mappature IPA/PA complesse per gli ospiti, creando l'illusione di una memoria contigua per gli ospiti nonostante la frammentazione fisica. Tuttavia, l'utilizzo dell'MMU di secondo livello per l'host è limitato solo al controllo dell'accesso. La fase 2 dell'host è mappata in base all'identità, in modo che la memoria contigua nello spazio IPA dell'host sia contigua nello spazio PA. Questa architettura consente l'utilizzo di mappature di grandi dimensioni nella tabella delle pagine e di conseguenza riduce la pressione sul Translation Lookaside Buffer (TLB). Poiché una mappatura delle identità può essere indicizzata dalla PA, il livello 2 dell'host viene utilizzato anche per monitorare la proprietà della pagina direttamente nella tabella delle pagine.

Protezione dell'accesso diretto alla memoria (DMA)

Come descritto in precedenza, lo scollegamento delle pagine guest dall'host Linux nelle tabelle di pagine della CPU è un passaggio necessario, ma non sufficiente, per proteggere la memoria guest. pKVM deve anche proteggere dagli accessi alla memoria effettuati da dispositivi compatibili con DMA sotto il controllo del kernel dell'host e dalla possibilità di un attacco DMA avviato da un host malintenzionato. Per impedire a un dispositivo di questo tipo di accedere alla memoria guest, pKVM richiede un'unità di gestione della memoria input-output (IOMMU) hardware per ogni dispositivo compatibile con DMA nel sistema, come mostrato nella figura 3.

Protezione dell'accesso alla memoria DMA

Figura 3. Protezione dell'accesso alla memoria DMA

Come minimo, l'hardware IOMMU fornisce i mezzi per concedere e revocare l'accesso in lettura/scrittura di un dispositivo alla memoria fisica con granularità a livello di pagina. Tuttavia, questo hardware IOMMU limita l'utilizzo dei dispositivi nelle pVM poiché presuppone un livello 2 mappato in base all'identità.

Per garantire l'isolamento tra le macchine virtuali, le transazioni di memoria generate per conto di entità diverse devono essere distinguibili dall'IOMMU in modo che sia possibile utilizzare l'insieme appropriato di tabelle di pagine per la traduzione.

Inoltre, la riduzione della quantità di codice specifico per SoC in EL2 è una strategia chiave per ridurre la base di calcolo attendibile (TCB) complessiva della pKVM e contrasta l'inclusione dei driver IOMMU nell'hypervisor. Per attenuare questo problema, l'host in EL1 è responsabile delle attività di gestione IOMMU ausiliarie, come la gestione dell'alimentazione, l'inizializzazione e, se opportuno, la gestione delle interruzioni.

Tuttavia, l'assegnazione del controllo dello stato del dispositivo all'host impone requisiti aggiuntivi all'interfaccia di programmazione dell'hardware IOMMU per garantire che i controlli delle autorizzazioni non possano essere aggirati con altri mezzi, ad esempio dopo un ripristino dei dati di fabbrica del dispositivo.

Un IOMMU standard e ben supportato per i dispositivi Arm che consente sia l'isolamento sia l'assegnazione diretta è l'architettura SMMU (System Memory Management Unit) di Arm. Questa architettura è la soluzione di riferimento consigliata.

Proprietà della memoria

Al momento dell'avvio, si presume che tutta la memoria non dell'hypervisor sia di proprietà dell'host e viene monitorata come tale dall'hypervisor. Quando viene generata una VM personale, l'host dona le pagine di memoria per consentirne l'avvio e l'hypervisor trasferisce la proprietà di queste pagine dall'host alla VM personale. Pertanto, l'hypervisor applica limitazioni di controllo dell'accesso nella tabella delle pagine della fase 2 dell'host per impedire all'utente ospite di accedere di nuovo alle pagine, garantendo la riservatezza.

La comunicazione tra l'host e gli ospiti è resa possibile dalla condivisione controllata della memoria tra di loro. Gli ospiti possono condividere alcune delle loro pagine con l'host utilizzando un'iperchiamata, che indica all'hypervisor di rimappare quelle pagine nella tabella delle pagine della fase 2 dell'host. Analogamente, la comunicazione dell'host con TrustZone è resa possibile da operazioni di condivisione e/o prestito della memoria, tutte monitorate e controllate attentamente da pKVM utilizzando la specifica Firmware Framework for Arm (FF-A).

Poiché i requisiti di memoria di una pVM possono cambiare nel tempo, viene fornita un'iperchiamata che consente di cedere nuovamente all'host la proprietà di pagine specifiche appartenenti all'autore della chiamata. In pratica, questa chiamata ipervisiva viene utilizzata con il protocollo virtio balloon per consentire al VMM di richiedere la memoria dalla VM e alla VM di notificare al VMM le pagine cedute in modo controllato.

L'hypervisor è responsabile del monitoraggio della proprietà di tutte le pagine di memoria nel sistema e se sono condivise o concesse in prestito ad altre entità. La maggior parte di questo monitoraggio dello stato viene eseguita utilizzando i metadati associati alle tabelle di pagine di primo livello dell'host e degli ospiti, utilizzando bit riservati nelle voci della tabella di pagine (PTE), che, come suggerisce il nome, sono riservate all'uso del software.

L'host deve assicurarsi di non tentare di accedere alle pagine rese inaccessibili dall'hypervisor. Un accesso illegale all'host causa l'inserimento di un'eccezione sincrona nell'host da parte dell'hypervisor, che può comportare la ricezione di un segnale SEGV da parte dell'attività nello spazio utente responsabile o l'arresto anomalo del kernel dell'host. Per evitare accessi accidentali, le pagine donate agli ospiti vengono dichiarate non idonee per lo scambio o l'unione dal kernel host.

Gestione delle interruzioni e timer

Le interruzioni sono una parte essenziale del modo in cui un utente interagisce con i dispositivi e per la comunicazione tra le CPU, dove le interruzioni interprocessore (IPI) sono il principale meccanismo di comunicazione. Il modello KVM prevede di delegare tutta la gestione delle interruzioni virtuali all'host in EL1, che a questo scopo si comporta come parte non attendibile dell'hypervisor.

pKVM offre un'emulazione completa del controller di interruzione generico (GICv3) di 3ª generazione basata sul codice KVM esistente. Il timer e gli IPI vengono gestiti nell'ambito di questo codice di emulazione non attendibile.

Supporto di GICv3

L'interfaccia tra EL1 ed EL2 deve garantire che lo stato completo delle interruzioni sia visibile all'host EL1, incluse le copie dei registri dell'hypervisor relativi alle interruzioni. In genere, questa visibilità viene ottenuta utilizzando regioni di memoria condivisa, una per CPU virtuale (vCPU).

Il codice di supporto del runtime del registro di sistema può essere semplificato in modo da supportare solo la cattura dei registri SGIR (Software Generated Interrupt Register) e DIR (Deactivate Interrupt Register). L'architettura prevede che questi registri generino sempre una trap in EL2, mentre finora le altre trap sono state utili solo per mitigare gli errori. Tutto il resto viene gestito nell'hardware.

Sul lato MMIO, tutto viene emulato a EL1, riutilizzando tutta l'attuale infrastruttura in KVM. Infine, Wait for Interrupt (WFI) viene sempre inoltrato a EL1, perché questa è una delle primitive di pianificazione di base utilizzate da KVM.

Supporto del timer

Il valore del comparatore per il timer virtuale deve essere esposto a EL1 su ogni WFI di trapping in modo che EL1 possa iniettare interruzioni del timer mentre la vCPU è bloccata. Il timer fisico è completamente emulato e tutte le trappole vengono ritrasmesse a EL1.

Gestione MMIO

Per comunicare con il monitor delle macchine virtuali (VMM) ed eseguire l'emulazione GIC, le trap MMIO devono essere ritrasmesse all'host in EL1 per un'ulteriore valutazione. pKVM richiede quanto segue:

  • IPA e dimensioni dell'accesso
  • Dati in caso di scrittura
  • Endianness della CPU al momento della trappola

Inoltre, le trappe con un registro generico (GPR) come fonte/destinazione vengono ritrasmesse utilizzando un pseudo-registro di trasferimento astratto.

Interfacce guest

Un ospite può comunicare con un ospite protetto utilizzando una combinazione di iperchiamate e accesso alla memoria alle regioni intrappolate. Le chiamate iperserie vengono esposte in base allo standard SMCCC, con un intervallo riservato per un'allocazione del fornitore da parte di KVM. Le seguenti chiamate ipervoci sono di particolare importanza per gli ospiti pKVM.

Iperchiamate generiche

  • PSCI fornisce un meccanismo standard per consentire all'ospite di controllare il ciclo di vita delle sue vCPU, inclusa l'attivazione, la disattivazione e l'arresto del sistema.
  • La TRNG fornisce un meccanismo standard per consentire all'ospite di richiedere entropia al pKVM, che inoltra la chiamata a EL3. Questo meccanismo è particolarmente utile se non è possibile considerare attendibile l'host per la virtualizzazione di un generatore di numeri random hardware (RNG).

Hypercall pKVM

  • Condivisione di ricordi con l'organizzatore. Inizialmente, tutta la memoria guest non è accessibile all'host, ma l'accesso all'host è necessario per la comunicazione con memoria condivisa e per i dispositivi paravirtualizzati che si basano su buffer condivisi. Le chiamate iper per condividere e annullare la condivisione di pagine con l'host consentono all'ospite di decidere esattamente quali parti di memoria devono essere rese accessibili al resto di Android senza dover eseguire un handshake.
  • Rinuncia alla memoria all'host. In genere, tutta la memoria guest appartiene all'ospite finché non viene distrutta. Questo stato può essere inadeguato per VM di lunga durata con requisiti di memoria che variano nel tempo. L'hypercall relinquish consente a un ospite di trasferire esplicitamente la proprietà delle pagine all'host senza richiedere la terminazione dell'ospite.
  • Trapping dell'accesso alla memoria all'host. Tradizionalmente, se un guest KVM accede a un indirizzo che non corrisponde a una regione di memoria valida, il thread vCPU esce nell'host e l'accesso viene in genere utilizzato per MMIO ed emulato dal VMM nello spazio utente. Per facilitare questa gestione, pKVM è tenuto a pubblicizzare i dettagli dell'istruzione con errore, come l'indirizzo, i parametri del registro e potenzialmente i relativi contenuti, all'host, il che potrebbe esporre involontariamente i dati sensibili di un guest protetto se la trappola non è stata prevista. pKVM risolve questo problema trattando questi errori come irreversibili, a meno che il guest non abbia precedentemente emesso un'iperchiamata per identificare l'intervallo IPA con errore come uno per il quale è consentito eseguire la trappola di nuovo all'host. Questa soluzione è indicata come MMIO guard.

Dispositivo I/O virtuale (virtio)

Virtio è uno standard popolare, portatile e maturo per l'implementazione e l'interazione con i dispositivi paravirtualizzati. La maggior parte dei dispositivi esposti agli ospiti protetti è implementata utilizzando virtio. Virtio è alla base anche dell'implementazione di vsock utilizzata per la comunicazione tra un guest protetto e il resto di Android.

I dispositivi Virtio vengono in genere implementati nello spazio utente dell'host dal VMM, che intercetta gli accessi alla memoria intrappolati dal guest all'interfaccia MMIO del dispositivo Virtio ed emula il comportamento previsto. L'accesso MMIO è relativamente costoso perché ogni accesso al dispositivo richiede un viaggio di andata e ritorno al VMM e viceversa, quindi la maggior parte del trasferimento effettivo dei dati tra il dispositivo e il guest avviene utilizzando un insieme di virtqueue in memoria. Un presupposto fondamentale di virtio è che l'host può accedere arbitrariamente alla memoria guest. Questa assunzione è evidente nel design della virtqueue, che potrebbe contenere puntatori ai buffer nell'ospite a cui l'emulazione del dispositivo dovrebbe accedere direttamente.

Sebbene le iperchiamate di condivisione della memoria descritte in precedenza possano essere utilizzate per condividere i buffer di dati virtio dall'ospite all'host, questa condivisione viene necessariamente eseguita a livello di granularità della pagina e potrebbe finire per esporre più dati del necessario se la dimensione del buffer è inferiore a quella di una pagina. Al contrario, il guest è configurato per allocare sia le virtqueue sia i relativi buffer di dati da una finestra fissa di memoria condivisa, con i dati che vengono copiati (ribattuti) da e verso la finestra in base alle esigenze.

Dispositivo virtuale

Figura 4. Dispositivo Virtio

Interazione con TrustZone

Sebbene gli ospiti non siano in grado di interagire direttamente con TrustZone, l'host deve essere ancora in grado di emettere chiamate SMC nel mondo sicuro. Queste chiamate possono specificare buffer di memoria con indirizzi fisici non accessibili all'host. Poiché il software sicuro in genere non è a conoscenza dell'accessibilità del buffer, un host malintenzionato potrebbe utilizzare questo buffer per eseguire un attacco di tipo confused deputy (analogo a un attacco DMA). Per impedire questi attacchi, la pKVM intercetta tutte le chiamate SMC dell'host all'EL2 e agisce come proxy tra l'host e il monitor sicuro a EL3.

Le chiamate PSCI dall'host vengono inoltrate al firmware EL3 con modifiche minime. Nello specifico, il punto di contatto per una CPU che viene messa online o riprende da una sospensione viene riscritto in modo che la tabella di pagine di livello 2 venga installata in EL2 prima di tornare all'host in EL1. Durante l'avvio, questa protezione viene applicata da pKVM.

Questa architettura si basa sull'uso di un SoC che supporta PSCI, preferibilmente tramite una versione aggiornata di TF-A come firmware EL3.

Firmware Framework for Arm (FF-A) standardizza le interazioni tra i mondi normale e sicuro, in particolare in presenza di un hypervisor sicuro. Una parte importante della specifica definisce un meccanismo per la condivisione della memoria con il mondo sicuro, utilizzando sia un formato di messaggio comune sia un modello di autorizzazioni ben definito per le pagine sottostanti. Il proxy pKVM dei messaggi FF-A garantisce che l'host non tenti di condividere la memoria con il lato sicuro per il quale non ha autorizzazioni sufficienti.

Questa architettura si basa sul software del mondo sicuro che applica il modello di accesso alla memoria per garantire che le app attendibili e qualsiasi altro software in esecuzione nel mondo sicuro possano accedere alla memoria solo se è di proprietà esclusiva del mondo sicuro o è stata condivisa esplicitamente con esso utilizzando FF-A. Su un sistema con S-EL2, l'applicazione del modello di accesso alla memoria deve essere eseguita da un SPMC (Secure Partition Manager Core), come Hafnium, che gestisce le tabelle di pagine di secondo livello per il mondo sicuro. In un sistema senza S-EL2, la TEE può invece applicare un modello di accesso alla memoria tramite le sue tabelle di pagine di primo livello.

Se la chiamata SMC a EL2 non è una chiamata PSCI o un messaggio definito da FF-A, le SMC non gestite vengono inoltrate a EL3. Si presume che il firmware sicuro (necessariamente attendibile) possa gestire in sicurezza le SMC non gestite perché il firmware comprende le precauzioni necessarie per mantenere l'isolamento delle pVM.

Monitor delle macchine virtuali

crosvm è un monitor delle macchine virtuali (VMM) che esegue le macchine virtuali tramite l'interfaccia KVM di Linux. Ciò che rende crosvm unico è il suo attenzione sulla sicurezza con l'utilizzo del linguaggio di programmazione Rust e una sandbox intorno ai dispositivi virtuali per proteggere il kernel host. Per saperne di più su crosvm, consulta la documentazione ufficiale qui.

Descrittori file e ioctl

KVM espone il dispositivo carattere /dev/kvm allo spazio utente con gli ioctl che costituiscono l'API KVM. Gli ioctl appartengono alle seguenti categorie:

  • I comandi ioctl di sistema eseguono query e impostano attributi globali che influiscono sull'intero sottosistema KVM e creano pVM.
  • I comandi ioctl della VM eseguono query e impostano attributi che creano CPU virtuali (vCPU) e dispositivi e influiscono su un'intera pVM, ad esempio il layout della memoria e il numero di CPU virtuali (vCPU) e dispositivi.
  • I comandi ioctl vCPU eseguono query e impostano attributi che controllano il funzionamento di una singola CPU virtuale.
  • I controlli ioctl del dispositivo eseguono query e impostano attributi che controllano il funzionamento di un singolo dispositivo virtuale.

Ogni processo crosvm esegue esattamente un'istanza di una macchina virtuale. Questo processo utilizza l'ioctl di sistema KVM_CREATE_VM per creare un descrittore file VM che può essere utilizzato per emettere ioctl pVM. Un ioctl KVM_CREATE_VCPU o KVM_CREATE_DEVICE su un FD VM crea una vCPU/un dispositivo e restituisce un descrittore file che punta alla nuova risorsa. Gli ioctl su un FD vCPU o dispositivo possono essere utilizzati per controllare il dispositivo creato utilizzando l'ioctl su un FD VM. Per le vCPU, è inclusa l'importante attività di esecuzione del codice guest.

All'interno, crosvm registra i descrittori file della VM con il kernel utilizzando l'interfaccia epoll con attivazione a bordo. Il kernel invia quindi una notifica a crosvm ogni volta che c'è un nuovo evento in attesa in uno dei descrittori file.

pKVM aggiunge una nuova funzionalità, KVM_CAP_ARM_PROTECTED_VM, che può essere utilizzata per recuperare informazioni sull'ambiente pVM e configurare la modalità protetta per una VM. crosvm la utilizza durante la creazione della pVM se viene passato il flag --protected-vm per eseguire query e prenotare la quantità di memoria appropriata per il firmware della pVM e poi per attivare la modalità protetta.

Allocazione memoria

Una delle responsabilità principali di un VMM è allocare la memoria della VM e gestire il relativo layout della memoria. crosvm genera un layout della memoria fisso descritto in modo approssimativo nella tabella seguente.

FDT in modalità normale PHYS_MEMORY_END - 0x200000
Libera ...
Ramdisk ALIGN_UP(KERNEL_END, 0x1000000)
Kernel 0x80080000
Bootloader 0x80200000
FDT in modalità BIOS 0x80000000
Base della memoria fisica 0x80000000
Firmware pVM 0x7FE00000
Memoria del dispositivo 0x10000 - 0x40000000

La memoria fisica viene allocata con mmap e donata alla VM per compilare le sue regioni di memoria, chiamate memslot, con l'ioctl KVM_SET_USER_MEMORY_REGION. Pertanto, tutta la memoria della VM guest viene attribuita all'istanza crosvm che la gestisce e può comportare l'interruzione del processo (terminazione della VM) se l'host inizia a esaurire la memoria libera. Quando una VM viene arrestata, la memoria viene cancellata automaticamente dall'hypervisor e restituita al kernel dell'host.

In KVM standard, il VMM mantiene l'accesso a tutta la memoria guest. Con pKVM, la memoria guest viene rimappata dallo spazio di indirizzi fisico dell'host quando viene donata all'ospite. L'unica eccezione è la memoria condivisa esplicitamente dall'ospite, come per i dispositivi virtio.

Le regioni MMIO nello spazio degli indirizzi dell'ospite non vengono mappate. L'accesso a queste regioni da parte dell'utente ospite viene intercettato e genera un evento I/O sul file descriptor della VM. Questo meccanismo viene utilizzato per implementare i dispositivi virtuali. In modalità protetta, il guest deve confermare che una regione del suo spazio degli indirizzi viene utilizzata per l'MMIO utilizzando un'iperchiamata, per ridurre il rischio di fuga accidentale di informazioni.

Pianificazione

Ogni CPU virtuale è rappresentata da un thread POSIX e programmata dallo scheduler Linux dell'host. Il thread chiama l'ioctl KVM_RUN sull'FD vCPU, con conseguente passaggio dell'hypervisor al contesto vCPU guest. Il programmatore dell'host tiene conto del tempo trascorso in un contesto ospite come tempo utilizzato dal thread vCPU corrispondente. KVM_RUN viene restituito quando si verifica un evento che deve essere gestito dal VMM, ad esempio I/O, fine dell'interruzione o arresto della vCPU. Il VMM gestisce l'evento e richiama di nuovo KVM_RUN.

Durante KVM_RUN, il thread rimane preeleggibile dallo scheduler host, tranne per l'esecuzione del codice dell'hypervisor EL2, che non è preeleggibile. La VM guest stessa non dispone di un meccanismo per controllare questo comportamento.

Poiché tutti i thread vCPU sono pianificati come qualsiasi altra attività nello spazio utente, sono soggetti a tutti i meccanismi QoS standard. Nello specifico, ogni thread vCPU può essere associato a CPU fisiche, inserito in cpuset, potenziato o limitato utilizzando il capping dell'utilizzo, avere il criterio di priorità/pianificazione modificato e altro ancora.

Dispositivi virtuali

crosvm supporta una serie di dispositivi, tra cui:

  • virtio-blk per immagini disco composite, di sola lettura o di lettura e scrittura
  • vhost-vsock per la comunicazione con l'host
  • virtio-pci come trasporto virtio
  • RTC (Real Time Clock) pl030
  • UART 16550a per la comunicazione seriale

Firmware pVM

Il firmware della pVM (pvmfw) è il primo codice eseguito da una pVM, simile alla ROM di avvio di un dispositivo fisico. Lo scopo principale di pvmfw è avviare il bootloader per l'avvio sicuro e ricavare la secret univoca della pVM. L'utilizzo di pvmfw non è limitato a un sistema operativo specifico, ad esempio Microdroid, purché sia supportato da crosvm ed è stato firmato correttamente.

Il file binario pvmfw è archiviato in una partizione flash dello stesso nome e viene aggiornato tramite OTA.

Avvio del dispositivo

Alla procedura di avvio di un dispositivo compatibile con pKVM viene aggiunta la seguente sequenza di passaggi:

  1. Il bootloader di Android (ABL) carica pvmfw dalla partizione in memoria e verifica l'immagine.
  2. L'ABL ottiene i suoi segreti del Device Identifier Composition Engine (DICE) (Identificatori di dispositivi composti (CDI) e la catena di certificati DICE) da un Nucleo di attendibilità.
  3. L'ABL ricava i CDI necessari per pvmfw e li aggiunge al file binario pvmfw.
  4. L'ABL aggiunge un linux,pkvm-guest-firmware-memory node regione di memoria riservata al DT, che descrive la posizione e le dimensioni del file binario pvmfw e i secret derivati nel passaggio precedente.
  5. L'ABL trasferisce il controllo a Linux e Linux inizializza il pKVM.
  6. pKVM annulla la mappatura della regione di memoria pvmfw dalle tabelle di pagine di secondo livello dell'host e la protegge dall'host (e dagli ospiti) per tutto il tempo di attività del dispositivo.

Dopo l'avvio del dispositivo, Microdroid viene avviato seguendo i passaggi descritti nella sezione Boot sequence (Sequenza di avvio) del documento Microdroid.

Avvio della pVM

Quando viene creata una pVM, crosvm (o un altro VMM) deve creare un memslot sufficientemente grande da essere compilato con l'immagine pvmfw dall'hypervisor. Il VMM è inoltre limitato nell'elenco dei registri di cui può impostare il valore iniziale (x0-x14 per la vCPU principale, nessuno per le vCPU secondarie). I registri rimanenti sono riservati e fanno parte dell'ABI hypervisor-pvmfw.

Quando la VMP viene eseguita, l'hypervisor trasferisce prima il controllo della vCPU principale a pvmfw. Il firmware si aspetta che crosvm abbia caricato un kernel firmato AVB, che può essere un bootloader o qualsiasi altra immagine, e un FDT non firmato nella memoria a offset noti. pvmfw convalida la firma AVB e, se l'operazione va a buon fine, genera una struttura ad albero del dispositivo attendibile dal FDT ricevuto, cancella i relativi segreti dalla memoria e passa al punto di contatto del payload. Se uno dei passaggi di verifica non va a buon fine, il firmware emette un'iperchiamata PSCI SYSTEM_RESET.

Tra un avvio e l'altro, le informazioni sull'istanza pVM vengono archiviate in una partizione (dispositivo virtio-blk) e criptate con il secret di pvmfw per garantire che, dopo un riavvio, il secret venga eseguito nell'istanza corretta.