Como implementar o dm-verity

O Android 4.4 e versões mais recentes oferecem suporte à Inicialização verificada o recurso do kernel device-mapper-verity (dm-verity), que fornece transparência verificação de integridade de dispositivos em bloco. O dm-verity ajuda a prevenir rootkits persistentes que podem manter privilégios raiz e comprometer dispositivos. Isso ajuda os usuários do Android a ter certeza de que, ao inicializar um dispositivo, ele está no mesmo que foi usado pela última vez.

Aplicativos potencialmente nocivos (PHAs) com privilégios de raiz podem se esconder de programas de detecção e se mascarar. O software de rooting pode fazer porque costumam ser mais privilegiados que os detectores, permitindo que para "mentir" aos programas de detecção.

O recurso dm-verity permite que você olhe para um dispositivo de transferência por blocos, o armazenamento do sistema de arquivos e determinar se ele corresponde à configuração do Terraform. Ele faz isso usando uma árvore hash criptográfica. Para cada bloco (normalmente 4K), há um hash SHA256.

Como os valores de hash são armazenados em uma árvore de páginas, somente “raiz” O hash precisa ser confiável para verificar o restante da árvore. A capacidade de modificar qualquer um dos blocos equivale a quebrar o hash criptográfico. Confira o diagrama a seguir para ver uma representação dessa estrutura.

dm-verity-hash-table

Figura 1. Tabela de hash dm-verity

Uma chave pública está incluída na partição de inicialização, que precisa ser verificada externamente pelo fabricante do dispositivo. Essa chave é usada para verificar a assinatura para esse hash e confirmar se a partição do sistema do dispositivo está protegida e sem alterações.

Operação

a proteção dm-verity fica no kernel. Portanto, se o acesso root em um software comprometer sistema antes do kernel surgir, ele vai reter esse acesso. Para reduzir isso a maioria dos fabricantes verifica o kernel usando uma chave gravada no dispositivo. Essa chave não poderá ser alterada depois que o dispositivo sair da fábrica.

Os fabricantes usam essa chave para verificar a assinatura no primeiro nível que, por sua vez, verifica a assinatura nos níveis subsequentes, pelo carregador de inicialização do aplicativo e, por fim, no kernel. Cada fabricante que queira aproveitar as vantagens verificadas boot precisa ter um método para verificar a integridade do kernel. Supondo que o kernel tenha sido verificado, ele pode examinar um dispositivo de transferência por blocos e verifique se está montado.

Uma maneira de verificar um dispositivo de transferência por blocos é gerar hash direto do conteúdo dele e comparar para um valor armazenado. No entanto, tentar verificar um dispositivo de transferência por blocos inteiro pode por um longo período e consomem grande parte da energia do dispositivo. Os dispositivos levariam por longos períodos para inicializar e, em seguida, ser significativamente drenados antes do uso.

Em vez disso, o dm-verity verifica os blocos individualmente e somente quando cada um é acessados. Quando lido na memória, o bloco recebe hash em paralelo. O hash é e verificar a árvore. E, como ler o bloco é um processo muito caro, operacional, a latência introduzida por essa verificação no nível do bloco comparativamente nominais.

Se a verificação falhar, o dispositivo vai gerar um erro de E/S indicando o bloco não pode ser lido. Parece que o sistema de arquivos foi corrompido, do jeito que está o esperado.

Os aplicativos podem continuar sem os dados resultantes, como quando esses resultados não são necessários para a função principal do aplicativo. No entanto, Se o aplicativo não puder continuar sem os dados, ele vai falhar.

Correção de erro de encaminhamento

O Android 7.0 e versões mais recentes melhoram a robustez da dm-verity com erro de encaminhamento. (FEC, na sigla em inglês). A implementação do AOSP começa com o Reed-Solomon (link em inglês) e aplica uma técnica chamada intercalação para reduzir a sobrecarga de espaço e aumentar a o número de blocos corrompidos que podem ser recuperados. Para mais detalhes sobre a FEC, consulte Inicialização verificada rigorosamente aplicada com correção de erros.

Implementação

Resumo

  1. Gerar uma imagem do sistema ext4.
  2. Gere uma árvore de hash para essa imagem.
  3. Crie uma tabela dm-verity para essa árvore de hash.
  4. Assine essa tabela dm-verity para produzir uma tabela. assinatura.
  5. Agrupe a assinatura da tabela e a tabela dm-verity em metadados de verdade.
  6. Concatenar a imagem do sistema, os metadados de veridade e a árvore hash.

Consulte Projetos do Chromium: inicialização verificada para uma descrição detalhada da árvore de hash e da tabela dm-verity.

Como gerar a árvore de hash

Conforme descrito na introdução, a árvore de hash é essencial para dm-verity. A ferramenta cryptsetup, gerar uma árvore de hash para você. Como alternativa, uma compatível é definida aqui:

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

Para formar o hash, a imagem do sistema é dividida na camada 0 em blocos 4k, cada atribuiu um hash SHA256. A camada 1 é formada pela junção apenas dos hashes SHA256. em blocos de 4K, resultando em uma imagem muito menor. A camada 2 é formada de maneira idêntica, com os hashes SHA256 da camada 1.

Isso é feito até que os hashes SHA256 da camada anterior caibam em um único bloco de recursos dependente. Ao receber o SHA256 desse bloco, você tem o hash raiz da árvore.

O tamanho da árvore de hash (e o uso de espaço em disco correspondente) varia de acordo com a da partição verificada. Na prática, o tamanho das árvores de hash tende a ser pequeno, geralmente menos de 30 MB.

Se você tem um bloco em uma camada que não é completamente preenchido naturalmente pelo da camada anterior, preencha-o com zeros para atingir o os 4K esperados. Isso permite que você saiba que a árvore de hash não foi removida e em vez de preencher com dados em branco.

Para gerar a árvore de hash, concatene os hashes da camada 2 com os da camada 1, da camada 3 os hashes para os da camada 2 e assim por diante. Escreva tudo isso para o disco. Observe que isso não faz referência à camada 0 do hash raiz.

Para recapitular, o algoritmo geral para construir a árvore de hash é o seguinte:

  1. Escolha um sal aleatório (codificação hexadecimal).
  2. Espalhe a imagem do sistema em blocos de 4K.
  3. Para cada bloco, receba o hash SHA256 (salgado) correspondente.
  4. Concatenar estes hashes para formar um nível
  5. Preencha o nível com 0s até um limite de bloco de 4k.
  6. Concatene o nível à sua árvore hash.
  7. Repita as etapas de 2 a 6 usando o nível anterior como fonte para o próximo até você tem apenas um hash.

O resultado disso é um hash único, que é o hash raiz. Isto e seu sal são usadas durante a construção da tabela de mapeamento dm-verity.

Como criar a tabela de mapeamento dm-verity

Crie a tabela de mapeamento dm-verity, que identifica o dispositivo (ou destino) de bloco para o kernel e a localização da árvore de hash (que tem o mesmo valor). Isso O mapeamento é usado para a geração e inicialização de fstab. A tabela também identifica o tamanho dos blocos e o hash_start, o local de início da árvore hash (especificamente, o número do bloco desde o início da imagem).

Consulte cryptsetup para uma descrição detalhada dos campos da tabela de mapeamento de destino de verdade.

Como assinar a tabela dm-verity

Assine a tabela dm-verity para produzir uma assinatura de tabela. Ao verificar um a assinatura da tabela é validada primeiro. Isso é feito em uma tecla a imagem de inicialização em um local fixo. Normalmente, as chaves são incluídas dos fabricantes criar sistemas para inclusão automática em dispositivos em um sistema o local.

Para verificar a partição com essa combinação de assinatura e chave:

  1. Adicione uma chave RSA-2048 em formato compatível com libmincrypt ao /boot partição às /verity_key. Identifique o local da chave usada para verificação a árvore hash.
  2. No fstab da entrada relevante, adicione verify às sinalizações fs_mgr.

Como agrupar a assinatura da tabela em metadados

Agrupar a assinatura da tabela e a tabela dm-verity em metadados do verity. Toda a de metadados é controlado por versão para que possa ser estendido, por exemplo, para adicionar um segundo um tipo de assinatura ou mudar alguma ordem.

Como verificação de integridade, um número mágico é associado a cada conjunto de metadados de tabela. que ajuda a identificar a tabela. Como o comprimento está incluído no sistema ext4, cabeçalho de imagem, isso fornece uma maneira de pesquisar os metadados sem saber o o conteúdo dos próprios dados.

Isso garante que você não verificou uma partição não verificada. Nesse caso, a ausência desse número mágico vai interromper o processo de verificação. Este número se assemelha a:
0xb001b001

Os valores de byte em hexadecimais são:

  • primeiro byte = b0
  • segundo byte = 01
  • terceiro byte = b0
  • quarto byte = 01

O diagrama a seguir mostra o detalhamento dos metadados de verdade:

<magic number>|<version>|<signature>|<table length>|<table>|<padding>
\-------------------------------------------------------------------/
\----------------------------------------------------------/   |
                            |                                  |
                            |                                 32K
                       block content

E esta tabela descreve esses campos de metadados.

Tabela 1. Campos de metadados do Verity

Campo Objetivo Tamanho Valor
número mágico usado por fs_mgr como verificação de integridade 4 bytes 0xb001b001
versão usada para controlar a versão do bloco de metadados 4 bytes 0 atualmente
assinatura a assinatura da tabela no formato PKCS1.5 preenchido 256 bytes
comprimento da tabela o tamanho da tabela dm-verity em bytes 4 bytes
tabela a tabela dm-verity descrita anteriormente bytes de tamanho de tabela
preenchimento esta estrutura é preenchida com 0 até 32k de comprimento 0

Otimização de dm-veridade

Para obter o melhor desempenho com dm-verity, você deve:

  • No kernel, ative o NEON SHA-2 para ARMv7 e SHA-2 extensões para ARMv8.
  • Teste diferentes tipos de read-ahead e prefetch_cluster configurações para encontrar a melhor configuração para seu dispositivo.