Android fornisce un'implementazione di riferimento di tutti i componenti necessari per implementare Android Virtualization Framework. Attualmente questa implementazione è limitata ad ARM64. Questa pagina spiega l'architettura del framework.
Premessa
L'architettura ARM consente fino a quattro livelli di eccezione, con il livello di eccezione 0 (EL0) che è il livello con privilegi minimi e il livello di eccezione 3 (EL3). La parte più grande del codebase Android (tutti i componenti dello spazio utente) viene eseguita su EL0. Il resto di ciò che viene comunemente chiamato "Android" è il kernel Linux, che viene eseguito su EL1.
Il livello EL2 consente l'introduzione di un hypervisor che consente di isolare la memoria e i dispositivi nelle singole pVM su EL1/EL0, con solide garanzie di riservatezza e integrità.
Hypervisor
La macchina virtuale protetta basata su kernel (pKVM) è basata sull'hypervisor KVM 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 alcune funzionalità della CPU, vale a dire le estensioni host di virtualizzazione (VHE) (ARMv8.1 e versioni successive). In una di queste modalità, comunemente nota come modalità non VHE, il codice hypervisor viene suddiviso dall'immagine del kernel durante l'avvio e installato su EL2, mentre il kernel stesso viene eseguito su EL1. Sebbene faccia parte del codebase Linux, il componente EL2 di KVM è un piccolo componente che si occupa del passaggio tra più EL1. Il componente hypervisor è compilato con Linux, ma risiede in una sezione di memoria separata e dedicata dell'immagine vmlinux
. pKVM sfrutta questo design estendendo il codice dell'hypervisor con nuove funzionalità che consentono di applicare restrizioni al kernel dell'host Android e allo spazio utente e limitando l'accesso dell'host alla memoria guest e all'hypervisor.
Moduli dei fornitori pKVM
Un modulo del fornitore pKVM è un modulo specifico per l'hardware contenente funzionalità specifiche del dispositivo, ad esempio i driver di IOMMU (Input-output Memory Management Unit). Questi moduli consentono di trasferire le funzionalità di sicurezza che richiedono l'accesso di livello di eccezione 2 (EL2) a pKVM.
Per informazioni su come implementare e caricare un modulo del fornitore pKVM, consulta Implementare un modulo del fornitore pKVM.
Procedura di avvio
La figura seguente illustra la procedura di avvio di pKVM:
- Il bootloader entra nel kernel generico in EL2.
- Il kernel generico rileva che è in esecuzione su EL2 e si toglie i privilegi a EL1 mentre pKVM e i suoi moduli continuano a essere eseguiti a EL2. Inoltre, al momento vengono caricati i moduli del fornitore pKVM.
- Il kernel generico si avvia normalmente, caricando tutti i driver del dispositivo necessari fino a raggiungere lo spazio utente. A questo punto, pKVM è attivo e gestisce le tabelle delle pagine della fase 2.
La procedura di avvio considera attendibile il bootloader per mantenere l'integrità dell'immagine del kernel solo durante l'avvio anticipato. Quando il kernel viene rimosso, non viene più considerato attendibile dall'hypervisor, che a sua volta è responsabile della sua protezione anche se il kernel viene compromesso.
Il kernel Android e l'hypervisor nella stessa immagine binaria consente un'interfaccia di comunicazione molto strettamente accoppiata tra loro. Questo accoppiamento stretto garantisce aggiornamenti atomici dei due componenti, evitando la necessità di mantenere stabile l'interfaccia tra i due componenti e offre una grande flessibilità senza compromettere la manutenibilità a lungo termine. L'accoppiamento stretto consente inoltre ottimizzazioni delle prestazioni quando entrambi i componenti possono cooperare 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 file 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 degli accessi a diverse parti della memoria. La MMU fase 1 è controllata da EL1 e consente un primo livello di traduzione degli indirizzi. La MMU fase 1 viene utilizzata da Linux per gestire lo spazio di indirizzi virtuali fornito a ogni processo dello spazio utente e al proprio spazio di indirizzi virtuali.
La MMU fase 2 è controllata da EL2 e consente l'applicazione di una traduzione del secondo indirizzo sull'indirizzo di output della MMU fase 1, ottenendo un indirizzo fisico (PA). La traslazione fase 2 può essere utilizzata dagli hypervisor per controllare e tradurre gli accessi alla memoria da tutte le VM guest. Come mostrato nella figura 2, quando entrambe le fasi della traduzione sono abilitate, 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.
In passato, KVM viene eseguito con la traduzione della fase 2 abilitata durante l'esecuzione degli guest e con la fase 2 disabilitata durante l'esecuzione del kernel Linux host. Questa architettura consente agli accessi alla memoria dalla MMU fase 1 dell'host di passare attraverso la MMU di fase 2, consentendo quindi l'accesso illimitato dalle pagine di memoria dell'host alle pagine di memoria degli ospiti. pKVM, invece, abilita la protezione di fase 2 anche nel contesto host e affida all'hypervisor la protezione delle pagine di memoria degli ospiti anziché dell'host.
KVM sfrutta appieno la traduzione degli indirizzi nella fase 2 per implementare mappature IPA/PA complesse per gli ospiti, creando l'illusione di memoria contigua per gli ospiti nonostante la frammentazione fisica. Tuttavia, l'utilizzo della MMU fase 2 per l'host è limitato solo al controllo dell'accesso. La fase 2 dell'host è mappata per identità, assicurando 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 buffer lookaside della traduzione (TLB). Poiché una mappatura delle identità può essere indicizzata dalla piattaforma di scambio pubblicitario, la fase 2 dell'host viene utilizzata anche per monitorare la proprietà della pagina direttamente nella tabella della pagina.
Protezione dell'accesso diretto alla memoria (DMA)
Come descritto in precedenza, l'annullamento della mappatura delle pagine guest dall'host Linux nelle tabelle delle pagine della CPU è un passaggio necessario, ma insufficiente per proteggere la memoria guest. pKVM deve anche proteggere dagli accessi alla memoria effettuati da dispositivi abilitati DMA sotto il controllo del kernel dell'host, e dalla possibilità di un attacco DMA avviato da un host dannoso. Per impedire a un dispositivo di questo tipo di accedere alla memoria guest, pKVM richiede hardware per unità di gestione della memoria di input-output (IOMMU) per ogni dispositivo con funzionalità DMA nel sistema, come mostrato nella figura 3.
Come minimo, l'hardware IOMMU consente di concedere e revocare l'accesso in lettura/scrittura di un dispositivo alla memoria fisica alla granularità della pagina. Tuttavia, questo hardware IOMMU limita l'uso dei dispositivi nelle pVM, in quanto presuppongono una fase 2 con mappatura delle identità.
Per garantire l'isolamento tra le macchine virtuali, le transazioni di memoria generate per conto di diverse entità devono essere distinguibili dall'IOMMU, in modo che possa essere utilizzato l'insieme appropriato di tabelle di pagina per la traduzione.
Inoltre, ridurre la quantità di codice specifico per il SoC in EL2 è una strategia chiave per ridurre la base di computing attendibile (TCB) complessiva di pKVM, in contrasto con l'inclusione dei driver IOMMU nell'hypervisor. Per limitare questo problema, l'host di EL1 è responsabile delle attività di gestione dell'IOMMU ausiliarie, come la gestione dell'alimentazione, l'inizializzazione e, ove appropriato, la gestione delle interruzioni.
Tuttavia, impostare l'host per controllare lo stato del dispositivo comporta ulteriori requisiti nell'interfaccia di programmazione dell'hardware IOMMU per garantire che i controlli delle autorizzazioni non possano essere aggirati in altri modi, ad esempio dopo un ripristino del dispositivo.
Un'IOMMU standard e ben supportata per i dispositivi ARM che rende possibili sia l'isolamento che l'assegnazione diretta è l'architettura SMMU (ARM System Memory Management Unit). Questa architettura è la soluzione di riferimento consigliata.
Proprietà della memoria
Al momento dell'avvio, si presume che tutta la memoria non hypervisor sia di proprietà dell'host e viene tracciata come tale dall'hypervisor. Quando viene generata una pVM, l'host dona pagine di memoria per consentirne l'avvio e l'hypervisor trasferisce la proprietà di queste pagine dall'host alla pVM. Pertanto, l'hypervisor applica restrizioni di controllo dell'accesso nella tabella delle pagine della fase 2 dell'host per impedirgli di accedere di nuovo alle pagine, garantendo riservatezza all'ospite.
La comunicazione tra l'host e gli ospiti è resa possibile dalla condivisione controllata della memoria tra loro. Gli ospiti possono condividere alcune delle loro pagine con l'host utilizzando una hypercall, che indica all'hypervisor di rimappare le pagine nella tabella delle pagine della fase 2 dell'host. Allo stesso modo, la comunicazione dell'host con TrustZone è resa possibile dalla condivisione della memoria e/o dalle operazioni di donazione, tutte strettamente monitorate e controllate 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 una hypercall che consente di cedere la proprietà di pagine specifiche appartenenti al chiamante all'host. In pratica, questa hypercall viene utilizzata con il protocollo Virtio Bubble per consentire al VMM di richiedere la memoria dalla pVM e alla pVM di notificare al VMM le pagine abbandonate, in modo controllato.
L'hypervisor è responsabile del monitoraggio della proprietà di tutte le pagine di memoria nel sistema e dell'eventuale condivisione o donazione ad altre entità. La maggior parte di questo monitoraggio dello stato viene eseguita utilizzando i metadati collegati alle tabelle di pagina della fase 2 dell'host e degli ospiti, utilizzando bit riservati nelle voci delle tabelle di pagina (PTE), che, come suggerisce il loro nome, sono riservati all'uso del software.
L'host deve assicurarsi di non tentare di accedere a pagine che sono state rese inaccessibili dall'hypervisor. Un accesso illegale all'host causa l'inserimento di un'eccezione sincrona nell'host da parte dell'hypervisor, il che può portare l'attività dello spazio utente responsabile a ricevere un segnale SEGV o l'arresto anomalo del kernel dell'host. Per impedire accessi accidentali, le pagine donate agli ospiti non sono idonee per lo scambio o l'unione da parte del kernel dell'host.
Gestione delle interruzioni e timer
Le interruzioni sono una parte essenziale del modo in cui un ospite interagisce con i dispositivi e per la comunicazione tra le CPU, dove gli interrupt dell'interprocessore (IPI) sono il principale meccanismo di comunicazione. Il modello KVM delega tutta la gestione delle interruzioni virtuali all'host in EL1, che a questo scopo si comporta come una parte attendibile dell'hypervisor.
pKVM offre un'emulazione GICv3 (Generic Interrupt Controller) versione 3 completa basata sul codice KVM esistente. Timer e IPI sono gestiti come parte di questo codice di emulazione non attendibile.
Supporto GICv3
L'interfaccia tra EL1 ed EL2 deve garantire che lo stato completo di interruzione sia visibile all'host EL1, comprese le copie dei registri hypervisor relativi agli interrupt. Questa visibilità si ottiene utilizzando regioni di memoria condivisa, una per CPU virtuale (vCPU).
Il codice di supporto del runtime del registro di sistema può essere semplificato per supportare solo il registro degli interrupt generati dal software (SGIR) e il trapping dei registri con disattivazione del registro di interruzione (DIR). L'architettura impone che questi registri siano sempre intrappolati a EL2, mentre le altre trappole sono state finora utili solo per mitigare gli errori. Tutto il resto viene gestito nell'hardware.
Sul lato MMIO, tutto è emulato su EL1, riutilizzando tutta l'infrastruttura attuale in KVM. Infine, l'opzione Attendi l'interruzione (WFI) viene sempre inoltrata a EL1, perché è una delle primitive di pianificazione di base utilizzate da KVM.
Supporto del timer
Il valore di confronto per il timer virtuale deve essere esposto a EL1 su ogni WFI intrappolante, in modo che EL1 possa inserire interruzioni del timer mentre la vCPU è bloccata. Il timer fisico è completamente emulato e tutte le trappole vengono inoltrate a EL1.
Gestione MMIO
Per comunicare con il monitor delle macchine virtuali (VMM) ed eseguire l'emulazione GIC, i trap MMIO devono essere inoltrati all'host in EL1 per un'ulteriore valutazione. pKVM richiede quanto segue:
- IPA e dimensioni dell'accesso
- Dati in caso di scrittura
- Endianità della CPU nel punto di trap
Inoltre, le trap con un registro per uso generico (GPR) come origine/destinazione vengono inoltrate utilizzando uno pseudo-registro di trasferimento astratto.
Interfacce ospiti
Un ospite può comunicare con un guest protetto utilizzando una combinazione di chiamate hypercall e accesso alla memoria alle regioni bloccate. Le hypercall sono esposte in base allo standard SMCC, con un intervallo riservato per un'allocazione di fornitore tramite KVM. Le seguenti hypercall sono di particolare importanza per gli ospiti pKVM.
Chiamate hypercall generiche
- PSCI fornisce all'ospite un meccanismo standard per controllare il ciclo di vita delle sue vCPU, tra cui on-line, offlining e arresto del sistema.
- TRNG fornisce un meccanismo standard che consente all'ospite di richiedere entropia da pKVM che inoltra la chiamata a EL3. Questo meccanismo è particolarmente utile quando non è possibile fidarsi dell'host per virtualizzare un generatore di numeri casuali hardware (RNG).
hypercall pKVM
- Condivisione della memoria con l'organizzatore. Tutta la memoria ospite è inizialmente inaccessibile all'host, ma l'accesso all'host è necessario per le comunicazioni con memoria condivisa e per i dispositivi paravirtualizzati che si basano su buffer condivisi. Le hypercall per la condivisione e l'annullamento della condivisione delle pagine con l'host consentono all'ospite di decidere esattamente quali parti di memoria sono rese accessibili al resto di Android senza la necessità di un handshake.
- Rilascio di memoria all'host. Tutta la memoria guest appartiene
all'utente fino a quando non viene eliminata. Questo stato può essere inadeguato per le 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 chiusura dell'utente. - Trapping degli accessi alla memoria all'host. Tradizionalmente, se un ospite KVM accede a un indirizzo che non corrisponde a una regione di memoria valida, il thread vCPU esce dall'host e l'accesso viene generalmente utilizzato per MMIO ed emulato dalla VMM nello spazio utente. Per facilitare questa gestione, pKVM deve pubblicizzare i dettagli sull'istruzione di errore, come l'indirizzo, i parametri di registrazione e potenzialmente i relativi contenuti, di nuovo all'host, il che potrebbe esporre involontariamente i dati sensibili di un guest protetto se il trap non era previsto. pKVM risolve questo problema trattando questi errori come irreversibile, a meno che l'ospite non abbia precedentemente emesso un hyperaccess per identificare il trap consentito per l'intervallo IPA consentito. Questa soluzione è detta Guard MMIO.
Dispositivo I/O virtuale (virtio)
Virtio è uno standard popolare, portatile e maturo per l'implementazione e l'interazione con dispositivi paravirtualizzati. La maggior parte dei dispositivi esposta agli ospiti protetti viene implementata utilizzando virtio. Virtio è alla base anche dell'implementazione di vsock utilizzata per la comunicazione tra un ospite protetto e il resto di Android.
I dispositivi Virtio sono in genere implementati nello spazio utente dell'host dal VMM, che intercetta gli accessi alla memoria intrappolata dall'ospite all'interfaccia MMIO del dispositivo virtio ed emula il comportamento previsto. L'accesso MMIO è relativamente costoso perché ogni accesso al dispositivo richiede un round trip verso il VMM e viceversa, quindi la maggior parte del trasferimento effettivo di dati tra il dispositivo e l'ospite avviene utilizzando un insieme di virtù in memoria. Un presupposto chiave di Virtio è che l'host possa accedere alla memoria guest in modo arbitrario. Questo presupposto è evidente nella progettazione della virtù, che potrebbe contenere puntatori ai buffer nel guest a cui l'emulazione del dispositivo deve accedere direttamente.
Anche se le hypercall di condivisione della memoria descritte in precedenza potrebbero essere utilizzate per condividere i buffer di dati Virtio dall'ospite all'host, questa condivisione viene necessariamente eseguita a livello di pagina e potrebbe esporre più dati del necessario se la dimensione del buffer è inferiore a quella di una pagina. Il guest è configurato invece per allocare sia le virtqueue che i buffer di dati corrispondenti da una finestra fissa di memoria condivisa, con i dati copiati (restituiti al mittente) da e verso la finestra a seconda delle esigenze.
Interazione con TrustZone
Anche se gli ospiti non possono interagire direttamente con TrustZone, l'host deve comunque essere in grado di inviare chiamate SMC nel mondo sicuro. Queste chiamate possono specificare buffer di memoria con indirizzi fisici inaccessibili all'host. Poiché il software sicuro in genere non è a conoscenza dell'accessibilità del buffer, un host dannoso potrebbe utilizzarlo per eseguire un attacco "deputy" confuso (analogico a un attacco DMA). Per prevenire questi attacchi, pKVM intrappola tutte le chiamate SMC dell'host a EL2 e agisce da proxy tra l'host e il monitoraggio sicuro di EL3.
Le chiamate PSCI dall'host vengono inoltrate al firmware EL3 con modifiche minime. Nello specifico, il punto di ingresso per una CPU che torna online o che riprende dalla sospensione viene riscritto in modo che la tabella della pagina della fase 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 sul SoC che supporta PSCI, preferibilmente tramite l'uso di una versione aggiornata di TF-A come firmware EL3.
Il framework firmware per ARM (FF-A) standardizza le interazioni tra il mondo normale e quello sicuro, soprattutto in presenza di un hypervisor sicuro. Una parte importante della specifica definisce un meccanismo per condividere la memoria con il mondo sicuro, utilizzando sia un formato di messaggio comune sia un modello di autorizzazioni ben definito per le pagine sottostanti. pKVM esegue il proxy dei messaggi FF-A per garantire che l'host non stia tentando di condividere la memoria con il lato sicuro per il quale non dispone di autorizzazioni sufficienti.
Questa architettura si basa sul software secure World che applica il modello di accesso alla memoria, per garantire che le app attendibili e qualsiasi altro software in esecuzione nel mondo sicuro possa accedere alla memoria solo se è di proprietà esclusiva del mondo sicuro o se è stata condivisa esplicitamente con il mondo protetto tramite FF-A. Su un sistema con S-EL2, l'applicazione del modello di accesso alla memoria deve essere eseguita da un Secure Partition Manager Core (SPMC), ad esempio Hafnium, che mantiene le tabelle di pagina di fase 2 per il mondo sicuro. Su un sistema senza S-EL2, il TEE può invece applicare un modello di accesso alla memoria tramite le tabelle di pagina di fase 1.
Se la chiamata SMC a EL2 non è una chiamata PSCI o un messaggio definito da FF-A, gli SMC non gestiti vengono inoltrati a EL3. Il presupposto è che il firmware protetto (necessariamente attendibile) possa gestire in sicurezza SMC non gestiti, poiché il firmware riconosce le precauzioni necessarie per mantenere l'isolamento della pVM.
Monitoraggio di macchine virtuali
crosvm è un monitor di macchine virtuali (VMM) che esegue le VM tramite l'interfaccia KVM di Linux. Ciò che rende unica crosvm è l'attenzione posta sulla sicurezza con l'uso del linguaggio di programmazione Rust e di una sandbox per i dispositivi virtuali per proteggere il kernel dell'host. Per scoprire di più su crosvm, consulta la documentazione ufficiale qui.
Descrittori e ioctl dei file
KVM espone il dispositivo a caratteri /dev/kvm
allo spazio utente con ioctl che compongono
l'API KVM. Gli ioctl appartengono alle seguenti categorie:
- Gli IoT di sistema eseguono query e impostano attributi globali che interessano l'intero sottosistema KVM e creano pVM.
- Gli IoT delle VM eseguono query e impostano attributi che creano CPU virtuali (vCPU) e dispositivi e influiscono su un'intera pVM, ad esempio includendo il layout della memoria e il numero di CPU virtuali (vCPU) e dispositivi.
- Gli IoT di vCPU eseguono query e impostano attributi che controllano il funzionamento di una singola CPU virtuale.
- Gli IoT dei dispositivi 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 di 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/dispositivo e restituisce un descrittore di file che punta alla
nuova risorsa. Gli ioctl su una vCPU o un dispositivo FD possono essere utilizzati per controllare il dispositivo
creato utilizzando l'Ioctl su un FD VM. Per le vCPU, è inclusa
l'attività importante dell'esecuzione del codice guest.
Internamente, crosvm registra i descrittori dei file della VM con il kernel utilizzando
l'interfaccia epoll
con attivazione perimetrale. Il kernel avvisa crosvm ogni volta
che c'è un nuovo evento in sospeso in uno dei descrittori del file.
pKVM aggiunge una nuova funzionalità, KVM_CAP_ARM_PROTECTED_VM
, che può essere utilizzata per ottenere informazioni sull'ambiente pVM e configurare la modalità protetta per una VM. crosvm la utilizza durante la creazione di una VM pVM se viene passato il flag --protected-vm
, per eseguire query e riservare la quantità di memoria appropriata per il firmware PVM e quindi per abilitare la modalità protetta.
Allocazione della memoria
Una delle responsabilità principali di un VMM è l'allocazione della memoria della VM e la gestione del relativo layout di memoria. crosvm genera un layout di memoria fisso descritto a grandi linee 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 di memoria fisica | 0x80000000
|
Firmware pVM | 0x7FE00000
|
Memoria del dispositivo | 0x10000 - 0x40000000
|
La memoria fisica viene allocata con mmap
e viene donata alla VM per
compilare le regioni di memoria, chiamate memslot, con
la classe KVM_SET_USER_MEMORY_REGION
. Tutta la memoria pVM guest viene quindi attribuita all'istanza crosvm che la gestisce e può comportare l'interruzione del processo (ovvero la chiusura 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 una KVM normale, il VMM conserva l'accesso a tutta la memoria guest. Con pKVM, la memoria ospite non viene mappata dallo spazio degli indirizzi fisici dell'host quando viene donata all'ospite. L'unica eccezione è la memoria esplicitamente condivisa dall'ospite, ad esempio per i dispositivi Virtio.
Le regioni MMIO nello spazio degli indirizzi dell'ospite non vengono mappate. L'accesso a queste regioni da parte dell'ospite è intrappolato e genera un evento I/O sul FD della VM. Questo meccanismo viene utilizzato per implementare i dispositivi virtuali. In modalità protetta, l'ospite deve confermare che una regione del suo spazio di indirizzi può essere utilizzata per l'MMIO tramite una chiamata hypercall, per ridurre il rischio di fuga accidentale di informazioni.
Pianificazione
Ogni CPU virtuale è rappresentata da un thread POSIX e pianificata dallo scheduler Linux host. Il thread chiama l'ioctl KVM_RUN
sul FD della vCPU, di conseguenza l'hypervisor passa al contesto della vCPU guest. Lo scheduler host considera il tempo trascorso in un contesto guest come tempo utilizzato dal thread delle vCPU corrispondente. KVM_RUN
restituisce quando si verifica un evento che deve essere gestito dalla VMM, come I/O, fine dell'interruzione o vCPU interrotta. Il VMM gestisce l'evento e chiama di nuovo KVM_RUN
.
Durante KVM_RUN
, il thread rimane prerilasciabile dallo scheduler host, ad eccezione
dell'esecuzione del codice dell'hypervisor EL2, che non è prerilasciabile. La VM guest
in sé non ha alcun meccanismo per controllare questo comportamento.
Poiché tutti i thread vCPU sono pianificati come qualsiasi altra attività dello spazio utente, sono soggetti a tutti i meccanismi di QoS standard. Nello specifico, ogni thread vCPU può essere associato a CPU fisiche, inserito in cpuset, potenziato o limitato tramite il blocco dell'utilizzo, modificato i criteri di priorità/pianificazione e altro ancora.
Dispositivi virtuali
crosvm supporta diversi dispositivi, tra cui:
- virtio-blk per immagini disco compositi, sola lettura o lettura/scrittura
- vhost-vsock per la comunicazione con l'host
- virtio-pci come trasporto virtio
- Orologio in tempo reale (RTC) pl030
- UART 16550a per la comunicazione seriale
Firmware pVM
Il firmware pVM (pvmfw) è il primo codice eseguito da una pVM, simile alla ROM di avvio di un dispositivo fisico. L'obiettivo principale di pvmfw è eseguire il bootstrap proteggendo l'avvio e ricavarne il secret univoco della pvm. L'uso di pvmfw non è limitato a qualsiasi sistema operativo specifico, ad esempio Microdroid, purché il sistema operativo sia supportato correttamente e sia supportato.
Il programma binario pvmfw viene memorizzato in una partizione Flash con lo stesso nome e viene aggiornato utilizzando OTA.
Avvio del dispositivo
La seguente sequenza di passaggi viene aggiunta alla procedura di avvio di un dispositivo abilitato per pKVM:
- Android Bootloader (ABL) carica pvmfw dalla sua partizione in memoria e verifica l'immagine.
- L'ABL ottiene i secret del proprio Device Identifier Compose Engine (DICE) (Compound Device Identifier (CDI) e la catena di certificati DICE) da una Root of Trust.
- L'ABL ricava i CDI necessari per pvmfw e li aggiunge al file binario pvmfw.
- L'ABL aggiunge al DT un nodo della regione di memoria riservata
linux,pkvm-guest-firmware-memory
, che descrive la posizione e le dimensioni del file binario pvmfw e i secret che ha derivato nel passaggio precedente. - La funzionalità ABL gestisce il controllo di Linux e Linux per inizializzare pKVM.
- pKVM annulla la mappatura della regione di memoria pvmfw dalle tabelle di pagine della fase 2 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 nella sezione Sequenza di avvio del documento Microdroid.
avvio pVM
Quando crei una pVM, crosvm (o un altro VMM) deve creare uno slot di memoria sufficientemente grande da completare 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 prenotati e fanno parte dell'ABI hypervisor-pvmfw.
Quando viene eseguita la pVM, il primo hypervisor passa 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 con offset noti. pvmfw convalida la firma AVB e, in caso di esito positivo, genera una struttura di dispositivi attendibile dal FDT ricevuto, cancella i suoi secret dalla memoria e si dirama al punto di ingresso del dispositivo. Se uno dei passaggi di verifica non va a buon fine, il firmware emette una hypercall SYSTEM_RESET
PSCI.
Tra gli avvii, le informazioni sull'istanza pVM vengono archiviate in una partizione (dispositivo virtio-blk) e criptate con il secret di pvmfw per garantire che, a seguito di un riavvio, venga eseguito il provisioning del secret per l'istanza corretta.