Microdroide

O Microdroid é um mini-SO Android 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 de PVMs não executam um SO independente, mas oferecem 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.

Com sistemas operacionais tradicionais, fornecer uma forte confidencialidade e integridade exige uma boa quantidade de 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 forma de carregar e executar com segurança parte do app na pVM, e o payload é criado com base no glibc. O app Android usa Bionic, a comunicação exige um protocolo personalizado over vsock e a depuração com adb é desafiadora.

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 para o Bionic, a comunicação acontece pelo Binder, permite a importação de APEXes do Android host e expõe um subconjunto da API do Android, como o keystore para operações criptográficas com chaves com suporte de hardware. No geral, os desenvolvedores vão encontrar o Microdroid como um ambiente familiar com as ferramentas que eles já estão acostumados no SO Android completo.

Recursos

O Microdroid é uma versão simplificada do Android com alguns componentes adicionais específicos para pVMs. O Microdroid oferece suporte a:

  • Um subconjunto de APIs do NDK (todas as APIs para a implementação do Android de libc e Bionic são fornecidas)
  • Recursos de depuração, como adb, logcat, tombstone e gdb
  • Inicialização verificada e SELinux
  • Como carregar e executar um binário, junto com bibliotecas compartilhadas, incorporado em um APK
  • RPC de Binder sobre vsock e troca de arquivos com verificações de integridade implícitas
  • Carregamento de APEXes

O Microdroid não oferece suporte a:

  • APIs Java do Android nos pacotes android.\*

  • SystemServer e Zygote

  • Gráficos/interface

  • HALs

Arquitetura do microdroide

O Microdroid é semelhante ao Cuttlefish, porque ambos têm uma arquitetura semelhante ao 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 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 são 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 de APEXes e APKs do Android.
  • instance: uma partição criptografada para manter os dados de inicialização verificados por instância, como sal por instância, chaves públicas confiáveis do APEX e contadores de reversão.

Sequência de inicialização

A sequência de inicialização do Microdroid ocorre após a inicialização do dispositivo. O boot do dispositivo é discutido na seção de firmware da pVM do documento Arquitetura. A Figura 1 mostra as etapas que ocorrem durante a sequência de inicialização do Microdroid:

Fluxo de inicialização seguro da instância do microdroide

Figura 1. Fluxo de inicialização segura da instância do microdroid

Confira uma explicação das etapas:

  1. O carregador de inicialização é carregado na memória pelo crosvm, e o pvmfw começa a ser executado. Antes de ir 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 OEM).
    • Garante que o mesmo carregador de inicialização seja usado de forma consistente em várias inicializações da mesma pVM pelo uso da imagem de instância. Especificamente, a pVM é inicializada inicialmente 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 descriptografará a identidade salva da imagem de instância e verificará se ela é a mesma que foi salva anteriormente. Se as identidades forem diferentes, o pvmfw não será inicializado.

    O carregador de inicialização inicializa o Microdroid.

  2. O carregador de inicialização acessa o disco da instância. Assim como o 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.

  3. O carregador de inicialização verifica o vbmeta e as partições conectadas, como boot e super, e, se bem-sucedido, extrai as chaves secretas de pVM da próxima etapa. Em seguida, o Microdroid transfere o controle para o kernel.

  4. Como a superpartição já foi verificada pelo carregador de inicialização (etapa 3), o kernel monta a superpartição de forma incondicional. Assim como no Android completo, a superpartição consiste em várias partições lógicas montadas em dm-verity. O controle é transmitido ao processo init, que inicia vários serviços nativos. O script init.rc é semelhante ao do Android completo, mas adaptado às necessidades do Microdroid.

  5. O processo init inicia o gerenciador do Microdroid, que acessa a imagem da instância. O serviço de gerenciamento 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 do cliente e dos APEXs em que o pVM confia. Essas informações são usadas mais tarde por zipfuse e apexd quando eles montam o APK do cliente e os APEXs solicitados, respectivamente.

  6. O serviço de gerenciamento do Microdroid inicia apexd.

  7. 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, …) e não de arquivos normais (/system/apex/*.apex).

  8. zipfuse é o sistema de arquivos FUSE do Microdroid. O zipfuse monta o APK do cliente, que é basicamente um arquivo ZIP como um sistema de arquivos. Abaixo, 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 APEXes que o desenvolvedor do app solicitou para essa instância pVM. A lista é usada por apexd ao ativar APEXes.

  9. O fluxo de inicialização retorna ao serviço de gerenciamento do Microdroid. O serviço de gerenciador se comunica com o VirtualizationService do Android usando o RPC do Binder para informar eventos importantes, como falha ou desligamento, e aceitar solicitações, como encerrar o pVM. O serviço de gerenciamento lê o local do binário principal do arquivo de configuração do APK e o executa.

Troca de arquivos (AuthFS)

É comum que os componentes do Android usem arquivos para entrada, saída e estado e os transmitam como descritores de arquivos (tipo ParcelFileDescriptor no AIDL) com acesso controlado pelo kernel do Android. O AuthFS facilita uma funcionalidade semelhante para trocar arquivos entre endpoints que não confiam mutuamente entre os limites da pVM.

Essencialmente, o AuthFS é um sistema de arquivos remoto com verificações de integridade transparentes em operações de acesso individuais, semelhante ao fs-verity. As verificações permitem que o front-end, como um programa de leitura de arquivos em execução em uma pVM, detecte se o back-end não confiável, normalmente o Android, adulterou o conteúdo do arquivo.

Para trocar arquivos, o back-end (fd\_server) é iniciado com a configuração por arquivo especificando se ele é para entrada (somente leitura) ou saída (leitura e gravação). Para a entrada, o front-end exige que o conteúdo corresponda a um hash conhecido, em cima de uma árvore Merkle para verificação no acesso. Para a saída, o AuthFS mantém internamente uma árvore hash do conteúdo conforme observado nas operações de gravação e pode aplicar a integridade quando os dados são lidos novamente.

No momento, o transporte é baseado no Binder RPC, mas isso pode mudar no futuro para otimizar o desempenho.

Gerenciamento de chaves

Os pVMs são fornecidos com uma chave de vedação estável que é adequada para proteger dados persistentes e uma chave de atestado adequada para produzir assinaturas que são comprovadamente produzidas pelo pVM.

RPC do Binder

A maioria das interfaces do Android é expressa em AIDL, que é criada sobre o driver do kernel do Binder Linux. Para oferecer suporte a interfaces entre pVMs, o protocolo Binder foi reescrito para funcionar em soquetes, vsock no caso de pVMs. A operação em soquetes permite que as interfaces AIDL do Android sejam usadas nesse novo ambiente.

Para configurar a conexão, um endpoint, como o payload 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.