O Microdroid é um mini SO Android que é executado em uma pVM. Não é necessário usar o Microdroid. É possível iniciar uma VM com qualquer SO. No entanto, os principais casos de uso para PVMs não são executar um SO independente, mas oferecer um ambiente de execução isolado para executar uma parte de um app com garantias de confidencialidade e integridade mais fortes do que o Android pode oferecer.
Com sistemas operacionais tradicionais, oferecer confidencialidade e integridade fortes exige bastante trabalho (muitas vezes duplicado) porque eles não se encaixam na arquitetura geral do Android. Por exemplo, com a arquitetura padrão do Android, os desenvolvedores precisam implementar uma maneira de carregar e executar com segurança parte do app na pVM, e o payload é criado com base na glibc. O app Android usa Bionic, a comunicação exige um protocolo personalizado sobre vsock, e a depuração usando adb é difícil.
O Microdroid preenche essas lacunas fornecendo uma imagem de SO pronta para uso projetada para exigir o mínimo de esforço dos desenvolvedores para descarregar uma parte do app em uma pVM. O código nativo é criado no Bionic, a comunicação acontece pelo Binder, e ele permite importar APEXes do Android host e expõe um subconjunto da API Android, como o keystore para operações criptográficas com chaves baseadas em hardware. No geral, os desenvolvedores vão achar o Microdroid um ambiente familiar com as ferramentas que já conhecem do SO Android completo.
Recursos
O Microdroid é uma versão simplificada do Android com alguns componentes adicionais específicos para pVMs. O Microdroid é compatível com:
- Um subconjunto de APIs do NDK (todas as APIs para a implementação do libc e do Bionic do Android são fornecidas)
- Recursos de depuração, como adb, logcat, tombstone e gdb
- Inicialização verificada e SELinux
- Carregar e executar um binário, junto com bibliotecas compartilhadas, incorporado em um APK
- RPC do Binder em vsock e troca de arquivos com verificações de integridade implícitas
- Carregamento de APEXes
O Microdroid não é compatível com:
APIs Java do Android nos pacotes
android.\*
SystemServer e Zygote
Gráficos/interface
HALs
Arquitetura do Microdroid
O Microdroid é semelhante ao Cuttlefish porque ambos têm uma arquitetura parecida com o Android padrão. O microdroid consiste nas seguintes imagens de partição agrupadas em uma imagem de disco composta:
bootloader
: verifica e inicia o kernel.boot.img
: contém o kernel e o ramdisk de inicialização.vendor_boot.img
: contém módulos de kernel específicos da VM, como o virtio.super.img
: consiste em partições lógicas do sistema e do fornecedor.vbmeta.img
: contém metadados de inicialização verificada.
As imagens de partição são enviadas no APEX de virtualização e empacotadas em
uma imagem de disco composta por VirtualizationService
. Além da imagem de disco composta do SO principal, VirtualizationService
é responsável por criar estas outras partições:
payload
: um conjunto de partições com suporte dos APEXs e APKs do Androidinstance
: uma partição criptografada para persistir dados de inicialização verificados por instância, como sal por instância, chaves públicas APEX confiáveis e contadores de rollback.
Sequência de inicialização
A sequência de inicialização do Microdroid ocorre após a inicialização do dispositivo. A inicialização do dispositivo é discutida na seção "Firmware da pVM" do documento Arquitetura. A Figura 1 mostra as etapas que ocorrem durante a sequência de inicialização do Microdroid:
Confira uma explicação das etapas:
O carregador de inicialização é carregado na memória pelo crosvm, e o pvmfw começa a execução. Antes de passar para o carregador de inicialização, o pvmfw executa duas tarefas:
- Verifica se o carregador de inicialização é de uma fonte confiável (Google ou um OEM).
- Garante que o mesmo carregador de inicialização seja usado de forma consistente em várias inicializações da mesma pVM usando a imagem da instância. Especificamente, a pVM é inicializada com uma imagem de instância vazia. O pvmfw armazena a identidade do carregador de inicialização na imagem da instância e a criptografa. Assim, na próxima vez que a pVM for inicializada com a mesma imagem de instância, o pvmfw vai descriptografar a identidade salva da imagem de instância e verificar se é a mesma que foi salva anteriormente. Se as identidades forem diferentes, o pvmfw vai se recusar a inicializar.
Em seguida, o carregador de inicialização inicia o Microdroid.
O carregador de inicialização acessa o disco da instância. Semelhante ao pvmfw, o bootloader tem uma unidade de disco de instância com informações sobre imagens de partição usadas nessa instância durante inicializações anteriores, incluindo a chave pública.
O carregador de inicialização verifica vbmeta e as partições encadeadas, como
boot
esuper
, e, se for bem-sucedido, deriva os segredos da pVM da próxima etapa. Em seguida, o Microdroid transfere o controle para o kernel.Como a superpartição já foi verificada pelo carregador de inicialização (etapa 3), o kernel monta a superpartição incondicionalmente. Assim como no Android completo, a superpartição consiste em várias partições lógicas montadas no dm-verity. Em seguida, o controle é transferido para o processo
init
, que inicia vários serviços nativos. O scriptinit.rc
é semelhante ao do Android completo, mas adaptado às necessidades do Microdroid.O processo
init
inicia o gerenciador do Microdroid, que acessa a imagem da instância. O serviço do gerenciador do Microdroid descriptografa a imagem usando a chave transmitida da etapa anterior e lê as chaves públicas e os contadores de reversão do APK e dos APEXs do cliente em que essa pVM confia. Essas informações são usadas mais tarde pelozipfuse
e peloapexd
ao montar o APK do cliente e os APEXs solicitados, respectivamente.O serviço do gerenciador do Microdroid inicia
apexd
.O
apexd
monta os APEXes nos diretórios/apex/<name>
. A única diferença entre como o Android e o Microdroid montam APEXes é que, no Microdroid, os arquivos APEX vêm de dispositivos de bloco virtuais (/dev/vdc1
, …), não de arquivos regulares (/system/apex/*.apex
).zipfuse
é o sistema de arquivos FUSE do Microdroid. Ozipfuse
monta o APK do cliente, que é essencialmente um arquivo Zip como um sistema de arquivos. Por baixo, o arquivo APK é transmitido como um dispositivo de bloco virtual pela pVM com dm-verity, assim como o APEX. O APK contém um arquivo de configuração com uma lista de APEXs que o desenvolvedor do app solicitou para esta instância de pVM. A lista é usada peloapexd
ao ativar APEXes.O fluxo de inicialização retorna ao serviço do gerenciador do Microdroid. O serviço manager se comunica com o
VirtualizationService
do Android usando Binder RPC para informar eventos importantes, como falhas ou desligamentos, e aceitar solicitações, como encerrar a pVM. O serviço de gerenciamento lê o local do binário principal no arquivo de configuração do APK e o executa.
Troca de arquivos (AuthFS)
É comum que componentes do Android usem arquivos para entrada, saída e estado
e os transmitam como descritores de arquivo (tipo ParcelFileDescriptor
em
AIDL) com acesso controlado pelo kernel do Android. O AuthFS facilita uma funcionalidade semelhante para troca de arquivos entre endpoints que não confiam uns nos outros em limites de pVM.
Basicamente, o AuthFS é um sistema de arquivos remoto com verificações de integridade transparentes
em operações de acesso individuais, semelhante a fs-verity
. As verificações permitem que o
front-end, como um programa de leitura de arquivos executado em uma pVM, detecte se o
backend não confiável, geralmente o Android, adulterou o conteúdo do arquivo.
Para trocar arquivos, o back-end (fd\_server
) é iniciado com uma configuração por arquivo que especifica se ele é destinado à entrada (somente leitura) ou à saída (leitura e gravação). Para entrada, o front-end exige que o conteúdo corresponda a um hash conhecido, além de uma árvore de Merkle para verificação no acesso. Para saída, o AuthFS mantém internamente uma árvore de hash do conteúdo, conforme observado nas operações de gravação, e pode impor a integridade quando os dados são lidos novamente.
No momento, o transporte subjacente é baseado em Binder RPC, mas isso pode mudar no futuro para otimizar a performance.
Gerenciamento de chaves
As pVMs são fornecidas com uma chave de lacre estável, adequada para proteger dados persistentes, e uma chave de atestado, adequada para produzir assinaturas que são comprovadamente geradas pela pVM.
RPC do Binder
A maioria das interfaces do Android é expressa em AIDL, que é criada com base no driver do kernel do Binder Linux. Para oferecer suporte a interfaces entre pVMs, o protocolo Binder foi reescrito para funcionar em sockets, vsock no caso de pVMs. A operação em sockets permite que as interfaces AIDL existentes do Android sejam usadas nesse novo ambiente.
Para configurar a conexão, um endpoint, como a carga útil da pVM, cria
um objeto RpcServer
, registra um objeto raiz e começa a detectar novas
conexões. Os clientes podem se conectar a esse servidor usando um objeto RpcSession
,
receber o objeto Binder
e usá-lo exatamente como um objeto Binder
é usado
com o driver Binder do kernel.