O Android oferece uma implementação de referência de todos os componentes necessários para implementar o framework de virtualização do Android. Atualmente, essa implementação está limitada ao ARM64. Nesta página, explicamos a arquitetura do framework.
Contexto
A arquitetura Arm permite até quatro níveis de exceção, sendo o nível de exceção 0 (EL0) o menos privilegiado e o nível de exceção 3 (EL3) o máximo. A maior parte da base de código do Android (todos os componentes do espaço do usuário) é executada em EL0. O restante do que é comumente chamado de "Android" é o kernel do Linux, que é executado em EL1.
A camada EL2 permite a introdução de um hipervisor que permite isolar memória e dispositivos em pVMs individuais em EL1/EL0, com fortes garantias de confidencialidade e integridade.
Hipervisor
A máquina virtual baseada em kernel protegida (pKVM) é criada com base no hipervisor KVM do Linux, que foi estendido com a capacidade de restringir o acesso aos payloads em execução em máquinas virtuais convidadas marcadas como "protegidas" no momento da criação.
O KVM/arm64 oferece suporte a diferentes modos de execução, dependendo da disponibilidade de
determinados recursos da CPU, como as Extensões de Host de Virtualização (VHE, na sigla em inglês) (ARMv8.1
e mais recentes). Em um desses modos, comumente conhecido como modo não VHE, o
código do hipervisor é dividido fora da imagem do kernel durante a inicialização e instalado no
EL2, enquanto o próprio kernel é executado em EL1. Embora faça parte da
base do código do Linux, o componente EL2 da KVM é um pequeno componente responsável pela
alternância entre vários EL1s. O componente do hipervisor é compilado com
o Linux, mas fica em uma seção de memória separada e dedicada da imagem
vmlinux
. O pKVM aproveita esse design estendendo o código do hipervisor com novos
recursos, permitindo colocar restrições no kernel do host do Android e no espaço
do usuário, além de limitar o acesso do host à memória do convidado e ao hipervisor.
Módulos de fornecedor pKVM
Um módulo de fornecedor pKVM é específico de hardware que contém funcionalidades específicas do dispositivo, como drivers de unidade de gerenciamento de memória de entrada/saída (IOMMU, na sigla em inglês). Com esses módulos, é possível fazer a portabilidade de recursos de segurança que exigem acesso de nível 2 de exceção (EL2) ao pKVM.
Para saber como implementar e carregar um módulo de fornecedor pKVM, consulte Implementar um módulo de fornecedor pKVM.
Procedimento de inicialização
A figura abaixo mostra o procedimento de inicialização do pKVM:
- O carregador de inicialização entra no kernel genérico em EL2.
- O kernel genérico detecta que está em execução no EL2 e remove o privilégio para EL1 enquanto o pKVM e os módulos dele continuam sendo executados no EL2. Além disso, os módulos do fornecedor pKVM são carregados nesse momento.
- O kernel genérico prossegue com a inicialização normalmente, carregando todos os drivers de dispositivo necessários até chegar ao espaço do usuário. Neste ponto, o pKVM já está em vigor e processa as tabelas de página do stage-2.
O procedimento de inicialização confia no carregador de inicialização para manter a integridade da imagem do kernel somente durante a inicialização antecipada. Quando o kernel é desprivilegiado, ele não é mais considerado confiável pelo hipervisor, que é responsável por se proteger, mesmo que o kernel esteja comprometido.
Ter o kernel do Android e o hipervisor na mesma imagem binária permite uma interface de comunicação muito acoplada entre eles. Esse acoplamento rígido garante atualizações atômicas dos dois componentes, o que evita a necessidade de manter a interface entre eles estável e oferece muita flexibilidade sem comprometer a manutenção a longo prazo. O acoplamento rígido também permite otimizações de desempenho quando os dois componentes podem cooperar sem afetar as garantias de segurança fornecidas pelo hipervisor.
Além disso, a adoção da GKI no ecossistema Android permite automaticamente que o hipervisor pKVM seja implantado em dispositivos Android no mesmo binário que o kernel.
Proteção contra acesso à memória da CPU
A arquitetura Arm especifica uma unidade de gerenciamento de memória (MMU, na sigla em inglês) dividida em dois estágios independentes. Ambos podem ser usados para implementar a conversão de endereços e o controle de acesso a diferentes partes da memória. A MMU de estágio 1 é controlada por EL1 e permite um primeiro nível de conversão de endereços. A MMU de estágio 1 é usada pelo Linux para gerenciar o espaço de endereço virtual fornecido a cada processo de espaço do usuário e ao próprio espaço de endereço virtual.
A MMU de estágio 2 é controlada pelo EL2 e permite a aplicação de uma segunda conversão de endereço no endereço de saída da MMU de estágio 1, resultando em um endereço físico (PA). A tradução no estágio 2 pode ser usada por hipervisores para controlar e traduzir acessos à memória de todas as VMs convidadas. Conforme mostrado na figura 2, quando os dois estágios de conversão estão ativados, o endereço de saída do estágio 1 é chamado de endereço físico intermediário (IPA). Observação: o endereço virtual (VA) é convertido em um IPA e depois em um PA.
Historicamente, a KVM é executada com a tradução do estágio 2 ativada durante a execução de convidados e com o estágio 2 desativado durante a execução do kernel do Linux do host. Essa arquitetura permite que os acessos à memória da MMU do estágio 1 do host passem pela MMU do estágio 2, permitindo o acesso irrestrito do host às páginas da memória do convidado. Por outro lado, a pKVM ativa a proteção do estágio 2 mesmo no contexto do host e coloca o hipervisor responsável por proteger as páginas da memória do convidado em vez do host.
A KVM faz uso integral da conversão de endereços no estágio 2 para implementar mapeamentos IPA/PA complexos para convidados, o que cria a ilusão de memória contígua para eles, apesar da fragmentação física. No entanto, o uso da MMU de estágio 2 para o host é restrito apenas ao controle de acesso. O estágio 2 do host é mapeado por identidade, garantindo que a memória contígua no espaço IPA do host seja contígua no espaço da PA. Essa arquitetura permite o uso de grandes mapeamentos na tabela de páginas e, consequentemente, reduz a pressão sobre o buffer de lookback de tradução (TLB, na sigla em inglês). Como um mapeamento de identidade pode ser indexado pela PA, o estágio do host 2 também é usado para rastrear a propriedade da página diretamente na tabela da página.
Proteção de acesso direto à memória (DMA)
Conforme descrito anteriormente, desmapear as páginas de convidado do host do Linux nas tabelas de páginas da CPU é uma etapa necessária, mas insuficiente, para proteger a memória do convidado. A pKVM também precisa proteger contra acessos à memória feitos por dispositivos compatíveis com DMA sob o controle do kernel do host e contra a possibilidade de um ataque de DMA iniciado por um host malicioso. Para evitar que esse dispositivo acesse a memória do convidado, a pKVM exige um hardware de unidade de gerenciamento de memória de entrada e saída (IOMMU, na sigla em inglês) para cada dispositivo compatível com DMA no sistema, conforme mostrado na Figura 3.
No mínimo, o hardware de IOMMU oferece meios de conceder e revogar o acesso de leitura/gravação de um dispositivo à memória física na granularidade da página. No entanto, esse hardware de IOMMU limita o uso de dispositivos em pVMs porque elas pressupõem um estágio 2 mapeado por identidade.
Para garantir o isolamento entre máquinas virtuais, as transações de memória geradas em nome de diferentes entidades precisam ser distinguíveis pela IOMMU, para que o conjunto apropriado de tabelas de páginas possa ser usado para a tradução.
Além disso, a redução da quantidade de código específico do SoC no EL2 é uma estratégia importante para reduzir a base de computação confiável (TCB, na sigla em inglês) geral da pKVM e vai contra a inclusão de drivers IOMMU no hipervisor. Para atenuar esse problema, o host em EL1 é responsável por tarefas de gerenciamento de IOMMU auxiliar, como gerenciamento de energia, inicialização e, quando apropriado, processamento de interrupções.
No entanto, colocar o host no controle do estado do dispositivo impõe outros requisitos à interface de programação do hardware IOMMU para garantir que as verificações de permissão não possam ser ignoradas por outros meios, por exemplo, após uma redefinição do dispositivo.
Uma IOMMU padrão e com suporte para dispositivos Arm que possibilita o isolamento e a atribuição direta é a arquitetura da unidade de gerenciamento de memória do sistema ARM (SMMU, na sigla em inglês). Essa arquitetura é a solução de referência recomendada.
Propriedade da memória
No momento da inicialização, presume-se que toda a memória não hipervisor pertence ao host, e é rastreada dessa forma pelo hipervisor. Quando uma pVM é gerada, o host doa páginas de memória para permitir a inicialização, e o hipervisor transfere a propriedade dessas páginas do host para a pVM. Assim, o hipervisor aplica restrições de controle de acesso na tabela de páginas do estágio 2 do host para evitar que ele acesse as páginas novamente, oferecendo confidencialidade ao convidado.
A comunicação entre o host e os convidados é possível graças ao compartilhamento controlado de memória entre eles. Os convidados podem compartilhar algumas das páginas deles com o host usando uma hiperchamada, que instrui o hipervisor a remapear essas páginas na tabela de páginas do estágio 2 do host. Da mesma forma, a comunicação do host com o TrustZone é possível por meio de operações de compartilhamento e/ou empréstimo de memória, todas monitoradas e controladas de perto pelo pKVM usando a especificação do Firmware Framework para Arm (FF-A, na sigla em inglês).
Como os requisitos de memória de uma pVM podem mudar ao longo do tempo, é fornecida uma hiperchamada que permite que a propriedade de páginas especificadas pertencentes ao autor da chamada seja renunciada ao host. Na prática, essa hiperchamada é usada com o protocolo de balão virtio para permitir que o VMM solicite a memória da pVM e para que a pVM notifique o VMM sobre páginas renunciadas de maneira controlada.
O hipervisor é responsável por rastrear a propriedade de todas as páginas de memória no sistema e se elas estão sendo compartilhadas ou emprestados a outras entidades. A maior parte desse rastreamento de estado é feito usando metadados anexados às tabelas de página do estágio 2 do host e dos convidados, usando bits reservados nas entradas da tabela da página (PTEs, na sigla em inglês) que, como sugere o nome, são reservados para uso do software.
O host precisa garantir que ele não tente acessar páginas que ficaram inacessíveis pelo hipervisor. Um acesso ilegal ao host faz com que uma exceção síncrona seja injetada no host pelo hipervisor, o que pode fazer com que a tarefa do espaço do usuário responsável receba um sinal SEGV ou que o kernel do host falhe. Para evitar acessos acidentais, as páginas doadas aos convidados não são qualificadas para troca ou mesclagem pelo kernel do host.
Tratamento de interrupções e timers
As interrupções são uma parte essencial da maneira como um convidado interage com os dispositivos e para a comunicação entre as CPUs, em que as interrupções entre processadores (IPIs) são o principal mecanismo de comunicação. O modelo KVM delega todo o gerenciamento de interrupções virtuais ao host no EL1, que, para essa finalidade, se comporta como uma parte não confiável do hipervisor.
O pKVM oferece uma emulação completa do controlador de interrupção genérica versão 3 (GICv3, na sigla em inglês) com base no código KVM. O timer e os IPIs são processados como parte desse código de emulação não confiável.
Suporte a GICv3
A interface entre EL1 e EL2 precisa garantir que o estado completo de interrupção esteja visível para o host EL1, incluindo cópias dos registros de hipervisor relacionados a interrupções. Essa visibilidade geralmente é alcançada usando regiões de memória compartilhada, uma por CPU virtual (vCPU).
O código de suporte ao tempo de execução do registro do sistema pode ser simplificado para oferecer suporte apenas ao Registro de interrupção gerada por software (SGIR, na sigla em inglês) e ao Desativar a detecção do registro de interrupção (DIR, na sigla em inglês). A arquitetura exige que esses registros sempre capturem o EL2, enquanto as outras armadilhas só foram úteis para mitigar errata até agora. Todo o resto é processado no hardware.
No MMIO, tudo é emulado no EL1, reutilizando toda a infraestrutura atual na KVM. Por fim, Aguardar interrupção (WFI) é sempre redirecionado para EL1, porque esse é um dos primitivos básicos de programação que a KVM usa.
Compatibilidade com temporizador
O valor do comparador para o timer virtual precisa ser exposto ao EL1 em cada WFI de captura para que o EL1 possa injetar interrupções de timer enquanto a vCPU estiver bloqueada. O timer físico é totalmente emulado e todos os armadilhas são retransmitidos para EL1.
Processamento de MMIO
Para se comunicar com o monitor de máquina virtual (VMM, na sigla em inglês) e executar a emulação de GIC, as armadilhas MMIO precisam ser redirecionadas de volta ao host no EL1 para uma maior triagem. O pKVM exige o seguinte:
- IPA e tamanho do acesso
- Dados no caso de uma gravação
- Endianness da CPU no ponto de interceptação
Além disso, as armadilhas com um registro de uso geral (GPR, na sigla em inglês) como origem/destino são retransmitidas usando um pseudoregistro de transferência abstrato.
Interfaces para visitantes
Um convidado pode se comunicar com outro protegido usando uma combinação de hiperchamadas e acesso à memória para regiões presas. As hiperchamadas são expostas de acordo com o padrão SMCCC (link em inglês), com um intervalo reservado para uma alocação de fornecedor pela KVM. As hiperchamadas a seguir são muito importantes para convidados do pKVM.
Hiperchamadas genéricas
- A PSCI oferece um mecanismo padrão para o convidado controlar o ciclo de vida das vCPUs, incluindo on-line, off-line e desligamento do sistema.
- O TRNG fornece um mecanismo padrão para o convidado solicitar entropia da pKVM, que redireciona a chamada para o EL3. Esse mecanismo é particularmente útil quando não é possível confiar no host para virtualizar um gerador de números aleatórios de hardware (RNG, na sigla em inglês).
Hiperchamadas pKVM
- Compartilhamento de memória com o host. Inicialmente, toda a memória do convidado fica inacessível para o host, mas esse acesso é necessário para a comunicação na memória compartilhada e para dispositivos paravirtualizados que dependem de buffers compartilhados. As hiperchamadas para compartilhar e cancelar o compartilhamento de páginas com o host permitem que o convidado decida exatamente quais partes da memória ficam acessíveis ao restante do Android sem a necessidade de um handshake.
- Renúncia de memória para o host. Toda a memória do convidado geralmente pertence
a ele até ser destruída. Esse estado pode ser inadequado para VMs de longa duração com requisitos de memória que variam com o tempo. A hiperchamada
relinquish
permite que um convidado transfira explicitamente a propriedade das páginas de volta ao host sem exigir o encerramento do convidado. - Captura de acesso à memória para o host. Tradicionalmente, se um convidado da KVM acessa um endereço que não corresponde a uma região de memória válida, a linha de execução de vCPU é encerrada para o host. O acesso normalmente é usado para MMIO e emulado pelo VMM no espaço do usuário. Para facilitar esse tratamento, a pKVM precisa divulgar detalhes sobre a instrução com falha, como endereço, registro de parâmetros e possivelmente o conteúdo de volta ao host, o que pode expor de maneira não intencional os dados sensíveis de um convidado protegido se a armadilha não tiver sido antecipada. O pKVM resolve esse problema tratando essas falhas como fatais, a menos que o convidado tenha emitido uma hiperchamada de IP para identificar a falha de IP. Essa solução é chamada de proteção MMIO.
Dispositivo virtual de E/S (virtio)
O Virtio é um padrão conhecido, portátil e maduro para implementação e interação com dispositivos paravirtualizados. A maioria dos dispositivos expostos a convidados protegidos é implementada usando o virtio. O Virtio também sustenta a implementação vsock usada para a comunicação entre um convidado protegido e o restante do Android.
Os dispositivos Virtio geralmente são implementados no espaço do usuário do host pelo VMM, que intercepta os acessos à memória presa do convidado para a interface MMIO do dispositivo virtio e emula o comportamento esperado. O acesso ao MMIO é relativamente caro, porque cada acesso ao dispositivo requer uma ida e volta ao VMM e ao VMM. Portanto, a maior parte da transferência de dados real entre o dispositivo e o convidado ocorre usando um conjunto de virtqueues na memória. Uma suposição fundamental do virtio é que o host pode acessar a memória do convidado arbitrariamente. Essa suposição é evidente no design da virtqueue, que pode conter ponteiros para buffers no convidado que a emulação do dispositivo pretende acessar diretamente.
Embora as hiperchamadas de compartilhamento de memória descritas anteriormente possam ser usadas para compartilhar buffers de dados virtio do convidado com o host, esse compartilhamento é necessariamente realizado na granularidade da página e pode acabar expondo mais dados do que o necessário se o tamanho do buffer for menor que o de uma página. Em vez disso, o convidado é configurado para alocar as virtqueues e os buffers de dados correspondentes de uma janela fixa de memória compartilhada, com os dados sendo copiados (rejeitados) para a janela conforme necessário.
Interação com o TrustZone
Embora os convidados não possam interagir diretamente com o TrustZone, o host ainda precisa ser capaz de emitir chamadas SMC para o mundo seguro. Essas chamadas podem especificar buffers de memória endereçados fisicamente que estão inacessíveis ao host. Como o software seguro geralmente não tem conhecimento da acessibilidade do buffer, um host malicioso pode usar esse buffer para realizar um ataque de deputado confuso (análogo a um ataque de DMA). Para evitar esses ataques, a pKVM captura todas as chamadas SMC do host para o EL2 e atua como um proxy entre o host e o monitor seguro em EL3.
As chamadas PSCI do host são encaminhadas para o firmware do EL3 com poucas modificações. Especificamente, o ponto de entrada de uma CPU que fica on-line ou retoma da suspensão é reescrito para que a tabela de páginas do estágio 2 seja instalada no EL2 antes de retornar ao host em EL1. Durante a inicialização, essa proteção é aplicada por pKVM.
Essa arquitetura depende do SoC compatível com o PSCI, de preferência pelo uso de uma versão atualizada do TF-A como firmware do EL3.
O Firmware Framework para Arm (FF-A, na sigla em inglês) padroniza as interações entre os mundos normal e seguro, especialmente na presença de um hipervisor seguro. Uma parte importante da especificação define um mecanismo para compartilhar memória com o mundo seguro, usando um formato de mensagem comum e um modelo de permissões bem definido para as páginas subjacentes. O pKVM envia mensagens FF-A para garantir que o host não esteja tentando compartilhar memória com o lado seguro para o qual não tem permissões suficientes.
Essa arquitetura depende do software de mundo seguro que aplica o modelo de acesso à memória, para garantir que apps confiáveis e qualquer outro software em execução no mundo seguro possam acessar a memória somente se ela for exclusivamente de propriedade do mundo seguro ou se tiver sido explicitamente compartilhada com ela usando FF-A. Em um sistema com S-EL2, a aplicação do modelo de acesso à memória deve ser feita por um Secure Partition Manager Core (SPMC), como o Hafnium, que mantém as tabelas de páginas do estágio 2 para o mundo seguro. Em um sistema sem S-EL2, o TEE pode aplicar um modelo de acesso à memória usando as tabelas de página do estágio 1.
Se a chamada SMC para EL2 não for uma chamada PSCI ou mensagem definida pelo FF-A, os SMCs não processados serão encaminhados para o EL3. A suposição é que o firmware seguro, necessariamente confiável, pode lidar com SMCs não processadas com segurança, porque o firmware entende as precauções necessárias para manter o isolamento da pVM.
Monitor de máquina virtual
O crosvm é um monitor de máquina virtual (VMM, na sigla em inglês) que executa máquinas virtuais por meio da interface KVM do Linux. O que torna o crosvm único é o foco em segurança com o uso da linguagem de programação Rust e um sandbox para dispositivos virtuais para proteger o kernel do host. Para saber mais sobre o crosvm, consulte a documentação oficial aqui (link em inglês).
Descritores de arquivos e ioctls
A KVM expõe o dispositivo de caractere /dev/kvm
ao espaço do usuário com ioctls que compõem
a API KVM. Os ioctls pertencem às seguintes categorias:
- Os ioctls do sistema consultam e definem atributos globais que afetam todo o subsistema KVM e criam pVMs.
- A VM ioctls consulta e define atributos que criam CPUs virtuais (vCPUs) e dispositivos e afetam uma pVM inteira, como incluir o layout da memória e o número de CPUs virtuais (vCPUs) e dispositivos.
- vCPU ioctls consulta e define atributos que controlam a operação de uma única CPU virtual.
- O ioctls do dispositivo consulta e define atributos que controlam a operação de um único dispositivo virtual.
Cada processo crosvm executa exatamente uma instância de uma máquina virtual. Esse processo
usa o ioctl do sistema KVM_CREATE_VM
para criar um descritor de arquivo de VM que pode
ser usado para emitir ioctls da pVM. Um ioctl de KVM_CREATE_VCPU
ou KVM_CREATE_DEVICE
em um FD de VM cria uma vCPU/dispositivo e retorna um descritor de arquivo que aponta para o
novo recurso. O ioctls em um FD de vCPU ou dispositivo pode ser usado para controlar o dispositivo
criado usando o ioctl em um FD de VM. Para vCPUs, isso inclui a
tarefa importante de executar o código de convidado.
Internamente, o crosvm registra os descritores de arquivos da VM com o kernel usando
a interface epoll
acionada por borda. Em seguida, o kernel notifica a crosvm sempre que
há um novo evento pendente em qualquer um dos descritores do arquivo.
A pKVM adiciona um novo recurso, KVM_CAP_ARM_PROTECTED_VM
, que pode ser usado para
receber informações sobre o ambiente de pVM e configurar o modo protegido para uma VM.
O crosvm usa isso durante a criação da pVM, se a sinalização --protected-vm
for transmitida,
para consultar e reservar a quantidade adequada de memória para
o firmware da pVM e, em seguida, para ativar o modo protegido.
Alocação de memória
Uma das principais responsabilidades de um VMM é alocar a memória da VM e gerenciar o layout dela. O crosvm gera um layout de memória fixo, que é descrito vagamente na tabela abaixo.
FDT no modo normal | PHYS_MEMORY_END - 0x200000
|
Lib espaço | ...
|
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000)
|
Kernel | 0x80080000
|
Carregador de inicialização | 0x80200000
|
FDT no modo BIOS | 0x80000000
|
Base da memória física | 0x80000000
|
firmware do pVM | 0x7FE00000
|
Memória do dispositivo | 0x10000 - 0x40000000
|
A memória física é alocada com mmap
e é doada à VM para preencher as regiões de memória, denominadas memslots, com o ioctl KVM_SET_USER_MEMORY_REGION
. Toda a memória da pVM convidada é, portanto,
atribuída à instância crosvm que a gerencia e pode resultar na
eliminação do processo (encerramento da VM) se o host começar a ficar sem
memória livre. Quando uma VM é interrompida, a memória é automaticamente apagada pelo hipervisor e retornada ao kernel do host.
Na KVM normal, o VMM mantém o acesso a toda a memória do convidado. Com o pKVM, a memória do convidado é desmapeada do espaço de endereço físico do host quando é doada ao convidado. A única exceção é a memória explicitamente compartilhada pelo convidado, como para dispositivos virtio.
As regiões MMIO no espaço de endereço do convidado são deixadas sem mapeamento. O acesso a essas regiões pelo convidado é bloqueado e resulta em um evento de E/S no FD da VM. Esse mecanismo é usado para implementar dispositivos virtuais. No modo protegido, o convidado precisa reconhecer que uma região do espaço de endereço é usada para MMIO por meio de uma hiperchamada para reduzir o risco de vazamento acidental de informações.
Programação
Cada CPU virtual é representada por uma linha de execução POSIX e programada pelo programador host
do Linux. A linha de execução chama o ioctl KVM_RUN
no FD da vCPU, resultando
na alternância do hipervisor para o contexto da vCPU do convidado. O programador do host considera o tempo gasto em um contexto de convidado como o tempo usado pela linha de execução de vCPU correspondente. KVM_RUN
é retornado quando há um evento que precisa ser processado pelo VMM, como E/S, fim da interrupção ou vCPU interrompida. O VMM processa o evento e chama KVM_RUN
novamente.
Durante KVM_RUN
, a linha de execução permanece preemptiva pelo programador do host, exceto
para a execução do código do hipervisor EL2, que não é preemptivo. A própria pVM
convidada não tem mecanismo para controlar esse comportamento.
Como todas as linhas de execução de vCPU são programadas como qualquer outra tarefa do espaço do usuário, elas estão sujeitas a todos os mecanismos QoS padrão. Especificamente, cada linha de execução de vCPU pode ser afinada a CPUs físicas, posicionadas em cpusets, otimizada ou limitada usando restrição de utilização, ter a política de prioridade/programação alterada e muito mais.
Dispositivos virtuais
O crosvm oferece suporte a vários dispositivos, entre eles:
- virtio-blk para imagens de disco composto, somente leitura ou leitura-gravação
- vhost-vsock para comunicação com o host
- virtio-pci como transporte virtio
- Relógio em tempo real pl030 (RTC)
- 16550a UART para comunicação serial
firmware do pVM
O firmware da pVM (pvmfw) é o primeiro código executado por uma pVM, semelhante à ROM de inicialização de um dispositivo físico. O objetivo principal do pvmfw é inicializar a inicialização segura e derivar o segredo exclusivo da pVM. O pvmfw não pode ser usado com qualquer SO específico, como o Microid, desde que o SO seja assinado e tenha suporte da VM.
O binário pvmfw é armazenado em uma partição flash com o mesmo nome e é atualizado usando OTA.
Inicialização do dispositivo
A sequência de etapas a seguir foi adicionada ao procedimento de inicialização de um dispositivo com pKVM:
- O carregador de inicialização do Android (ABL, na sigla em inglês) carrega o arquivo pvmfw da partição dele na memória e verifica a imagem.
- A ABL recebe as chaves secretas do Mecanismo de composição do identificador do dispositivo (DICE, na sigla em inglês), identificadores de dispositivos compostos (CDIs, na sigla em inglês) e cadeia de certificados DICE, de uma raiz de confiança.
- A ABL deriva as CDIs necessárias para pvmfw e as anexa ao binário pvmfw.
- A ABL adiciona um nó de região de memória reservado
linux,pkvm-guest-firmware-memory
à DT, descrevendo a localização e o tamanho do binário pvmfw e os secrets que ele derivou na etapa anterior. - O ABL transfere o controle para o Linux e o Linux inicializa a pKVM.
- O pKVM desmapea a região de memória pvmfw das tabelas de páginas do estágio 2 do host e a protege do host (e dos convidados) durante o tempo de atividade do dispositivo.
Após a inicialização do dispositivo, o Microdroid é inicializado de acordo com as etapas da seção Sequência de inicialização do documento Microdroid.
inicialização de pVM
Ao criar uma pVM, o crosvm (ou outro VMM) precisa criar um memslot grande o suficiente para ser preenchido com a imagem pvmfw pelo hipervisor. O VMM também está restrito na lista de registros cujo valor inicial ele pode definir (x0-x14 para a vCPU principal, nenhum para vCPUs secundárias). Os registros restantes são reservados e fazem parte da ABI hipervisor-pvmfw.
Quando a pVM é executada, o hipervisor primeiro passa o controle da vCPU primária
para o pvmfw. O firmware espera que o crosvm tenha carregado um kernel assinado pela AVB,
que pode ser um carregador de inicialização ou qualquer outra imagem e um FDT não assinado na memória em
deslocamentos conhecidos. O pvmfw valida a assinatura AVB e, se bem-sucedido,
gera uma árvore de dispositivos confiáveis do FDT recebido, limpa os secrets da
memória e ramificações para o ponto de entrada do payload. Se uma das
etapas de verificação falhar, o firmware emite uma hiperchamada SYSTEM_RESET
do PSCI.
Entre as inicializações, as informações sobre a instância pVM são armazenadas em uma partição (dispositivo virtio-blk) e criptografadas com o secret do pvmfw para garantir que, após uma reinicialização, o secret seja provisionado para a instância correta.