Architettura AVF

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

Background

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 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 del fornitore di pKVM

Un modulo del fornitore pKVM è un modulo specifico per l'hardware contenente funzionalità specifiche per il 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 informazioni su 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 pKVM

Figura 1. Procedura di avvio del 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 considera attendibile il bootloader per mantenere l'integrità dell'immagine del kernel solo durante l'avvio anticipato. Quando il kernel viene deprivato dei privilegi, non è più considerato attendibile dall'hypervisor, che è quindi responsabile della propria protezione anche se il kernel è compromesso.

Il kernel Android e l'hypervisor nella stessa immagine binaria consente un'interfaccia di comunicazione molto strettamente accoppiata tra loro. Questo abbinamento stretto garantisce aggiornamenti atomici dei due componenti, il che evita la necessità di 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. 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 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 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 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 memoria contigua per gli ospiti nonostante la frammentazione fisica. Tuttavia, l'utilizzo dell'MMU di Livello 2 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 buffer lookaside della traduzione (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 hardware per unità di gestione della memoria di input-output (IOMMU) per ogni dispositivo con funzionalità 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 VM parallele perché 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 il 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 reset 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 hypervisor sia di proprietà dell'host e viene tracciata 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 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 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 dalla 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, 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 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. Timer e IPI sono gestiti come parte 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 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 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 della macchina virtuale (VMM) ed eseguire l'emulazione GIC, le trap MMIO devono essere inoltrate all'host in EL1 per poter eseguire 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 a uso generale (GPR) come fonte/destinazione vengono ritrasmesse utilizzando un pseudo-registro di trasferimento astratto.

Interfacce per gli ospiti

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.

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.
  • 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 quando non è possibile fidarsi dell'host per virtualizzare un generatore di numeri casuali 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 la condivisione e la disattivazione della condivisione delle pagine con l'host consentono all'ospite di decidere esattamente quali parti della 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 dati sensibili da 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 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 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 chiave di Virtio è che l'host possa accedere alla memoria guest in modo arbitrario. Questa assunzione è evidente nel design della virtqueue, che potrebbe contenere puntatori ai buffer nell'ospite a cui l'emulazione del dispositivo dovrebbe 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 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 dannoso potrebbe utilizzarlo per eseguire un attacco "deputy" confuso (analogico a un attacco DMA). Per impedire questi attacchi, la pKVM intercetta tutte le chiamate SMC dell'host a 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 sul SoC che supporta PSCI, preferibilmente tramite l'uso di 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 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. 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 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 saperne di più su crosvm, consulta la documentazione ufficiale qui.

Descrittori e ioctl dei file

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 controlli ioctl di sistema eseguono query e impostano attributi globali che influiscono sull'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 l'inclusione del layout della memoria e del 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.
  • Gli 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/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'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 all'onda. Il kernel poi invia 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 della 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 indirizzi fisico 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'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 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 prerilasciabile dallo scheduler host, ad eccezione dell'esecuzione del codice dell'hypervisor EL2, che non è prerilasciabile. 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 pvmfw (pvmfw) è il primo codice eseguito da una pVM, simile alla ROM di avvio di un dispositivo fisico. L'obiettivo principale di pvmfw è quello di eseguire il bootstrap con l'avvio protetto e ricavare il secret univoco della VM. L'utilizzo di pvmfw non è limitato a un sistema operativo specifico, ad esempio Microdroid, purché il sistema operativo sia supportato correttamente

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

Avvio del dispositivo

La seguente sequenza di passaggi viene aggiunta alla procedura di avvio di un dispositivo abilitato per pKVM:

  1. Il bootloader di Android (ABL) carica pvmfw dalla sua partizione in memoria e verifica l'immagine.
  2. L'ABL ottiene i suoi segreti di Device Identifier Composition Engine (DICE) (Compound Device Identifiers (CDI) e la catena di certificati DICE) da un Root of Trust.
  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 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 viene eseguita la pVM, la prima parte dell'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 in 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 provisioning del secret venga eseguito nell'istanza corretta.