
Se você está preocupado com a integridade do seu sistema, dm-verity é uma das peças-chave do ecossistema Linux para inicializar com segurança e detectar adulterações de armazenamento. Originou-se como parte do mapeador de dispositivos do kernel e agora é a base para inicialização verificada no Android, OpenWrt e distribuições que buscam maior segurança.
Longe de ser um conceito abstrato, dm-verity é configurado e usado com ferramentas reais como veritysetup e systemd-veritysetupEle valida blocos dinamicamente usando árvores de hash e pode reagir à corrupção com políticas que vão desde o registro do evento até a reinicialização ou a falha do sistema. Vamos dar uma olhada mais de perto, sem deixar pontas soltas.
O que é dm-verity e por que você pode se importar
dm-verity é um alvo de mapeador de dispositivos no kernel que verifica a integridade de um dispositivo de bloco à medida que os dados são lidosEle funciona calculando e verificando hashes de cada bloco (geralmente 4K) em relação a uma árvore de hash pré-calculada, normalmente usando SHA-256.
Este projeto permite Os arquivos não podem ser modificados silenciosamente entre reinicializações ou durante a execuçãoÉ essencial estender a cadeia de confiança de inicialização para o sistema operacional, limitar a persistência de malware, fortalecer as políticas de segurança e garantir mecanismos de criptografia e MAC durante a inicialização.
No Android (desde 4.4) e no Linux em geral, A confiança está ancorada no hash raiz da árvore, que é assinada e validada com uma chave pública localizada em um local protegido (por exemplo, na partição de inicialização ou em um UKI assinado pelo Secure Boot). Quebrar qualquer bloco exigiria quebrar o hash criptográfico subjacente.
A verificação é feita por bloco e sob demanda: A latência adicionada é mínima em comparação com o custo de E/SSe uma verificação falhar, o kernel retornará um erro de E/S e o sistema de arquivos parecerá corrompido, o que é esperado quando os dados não são confiáveis. Os aplicativos podem decidir se continuam ou não com base em sua tolerância a falhas.
Como a árvore de verificação funciona internamente
A árvore de verificação é construída em camadas. A camada 0 são os dados brutos do dispositivo, divididos em blocos de 4K; um hash SHA-256 (salgado) é calculado para cada bloco. Esses hashes são então concatenados para formar a camada 1. A camada 1 é então agrupada em blocos e reprocessada para formar a camada 2, e assim por diante até que tudo caiba em um único bloco: esse bloco, quando processado, produz o hash raiz.
Se alguma camada não completar exatamente um bloco, É preenchido com zeros até atingir 4K para evitar ambiguidade. O tamanho total da árvore depende do tamanho da partição que está sendo verificada; na prática, geralmente é inferior a 30 MB para partições de sistema típicas.
O processo geral é: escolha um sal aleatório, faça hash para 4K, calcule SHA-256 com sal por bloco, concatena para formar níveis, preenche o limite do bloco com zeros e repete com o nível anterior até que reste um único hash raiz. Esse hash raiz, juntamente com o salt utilizado, alimenta a tabela dm-verity e a assinatura.
Versões e algoritmos de formato de disco
O formato dos blocos de hash no disco tem uma versão. A versão 0 foi a versão original usada no Chromium OS:O sal é adicionado no final do processo de hash, os resumos são armazenados continuamente e o restante do bloco é preenchido com zeros.
La A versão 1 é recomendada para novos dispositivos: O salt é adicionado ao hash, e cada resumo é preenchido com zeros até potências de dois, melhorando o alinhamento e a robustez. A tabela dm-verity também especifica o algoritmo (por exemplo, sha1 ou sha256), embora, para a segurança atual, sha256 seja usado.
tabela dm-verity e parâmetros essenciais
A tabela de destino dm-verity descreve onde estão os dados, onde está a árvore hash e como verificarCampos típicos da tabela:
- dev: dispositivo com os dados a serem verificados (tipo de caminho /dev/sdXN ou maior:menor).
- hash_dev: dispositivo com a árvore hash (pode ser o mesmo; se for, hash_start deve estar fora do intervalo verificado).
- tamanho_do_bloco_de_dados: tamanho do bloco de dados em bytes (por exemplo, 4096).
- tamanho_do_bloco_de_hash: tamanho do bloco de hash em bytes.
- num_data_blocks: número de blocos de dados verificáveis.
- bloco_de_início_de_hash: deslocamento (em blocos hash_block_size) para o bloco raiz da árvore.
- algoritmo: algoritmo de hash (por exemplo, sha256).
- digerir: codificação hexadecimal do hash do bloco raiz (incluindo salt de acordo com a versão do formato); este é o valor confiável.
- sal: sal hexadecimal.
Além disso, existem parâmetros opcionais muito útil para ajustar o comportamento:
- ignorar_corrupção: Registra blocos corrompidos, mas permite que a leitura continue.
- reiniciar_em_corrupção: reiniciar na detecção de corrupção (não compatível com ignore_corruption e requer suporte ao espaço do usuário para evitar loops).
- pânico_na_corrupção: : causa pânico ao detectar corrupção (não compatível com versões anteriores).
- restart_on_error y pânico_com_erro: mesmas reações, mas para erros de E/S.
- ignorar_zero_blocos: não verifica blocos que são esperados como zeros e retorna zeros.
- use_fec_do_dispositivo + raízes_fec + blocos_fec + fec_start: Habilita o Reed–Solomon (FEC) para recuperar dados quando a verificação falhar; os dados, o hash e as áreas FEC não devem se sobrepor, e os tamanhos dos blocos devem corresponder.
- verificar_no_máximo_uma_vez: Verifica cada bloco de dados somente na primeira vez que ele é lido (reduz a sobrecarga em detrimento da segurança em ataques ao vivo).
- root_hash_sig_key_desc: Referência a uma chave no chaveiro para validar uma assinatura PKCS7 do hash raiz ao criar o mapeamento (requer configuração de kernel apropriada e chaveiros confiáveis).
- tentar_verificar_na_tarefa: Se os hashes forem armazenados em cache e o tamanho de E/S permitir, verifica a metade inferior para reduzir a latência; ajustado com /sys/module/dm_verity/parameters/use_bh_bytes por classe de E/S.
Assinatura, metadados e ancoragem de confiança
Para que o dm-verity seja confiável, O hash raiz deve ser confiável e geralmente assinadoNo Android clássico, uma chave pública é incluída na partição de inicialização, que é verificada externamente pelo fabricante; ela valida a assinatura de hash raiz e garante que a partição do sistema não foi alterada.
Os metadados Verity adicionam estrutura e controle de versão. O bloco de metadados inclui um número mágico 0xb001b001 (bytes b0 01 b0 01), versão (atualmente 0), a assinatura da tabela em PKCS1.5 (normalmente 256 bytes para RSA-2048), o comprimento da tabela, a tabela em si e preenchimento de zeros até 32K.
Nas implementações do Android, a verificação depende de fs_mgr e fstab: Adicionando uma marca de seleção à entrada correspondente e colocando a chave em /boot/verity_key. Se o número mágico não estiver onde deveria estar, a verificação é interrompida para evitar verificar a coisa errada.
Início da operação verificado
A proteção reside no kernel: Se comprometido antes da inicialização do kernel, o invasor mantém o controleÉ por isso que os fabricantes normalmente validam rigorosamente cada estágio: uma chave gravada no dispositivo verifica o primeiro bootloader, que verifica o próximo, o bootloader do aplicativo e, finalmente, o kernel.
Com o kernel verificado, dm-verity é habilitado ao montar o dispositivo de bloco verificadoEm vez de executar o hash de todo o dispositivo (o que seria lento e desperdiçaria energia), ele é verificado bloco a bloco à medida que é acessado. Uma falha causa um erro de E/S, e os serviços e aplicativos reagem de acordo com sua tolerância: continuando sem esses dados ou travando completamente.
Correção de Erros Antecipada (FEC)
Desde o Android 7.0, FEC (Reed–Solomon) é incorporado com técnicas de entrelaçamento para reduzir o espaço e aumentar a capacidade de recuperação de blocos danificados. Isso funciona em conjunto com o dm-verity: se uma verificação falhar, o subsistema pode tentar corrigi-la antes de declará-la irrecuperável.
Desempenho e otimização
Para reduzir o impacto: Habilitar aceleração SHA-2 por NEON em ARMv7 e extensões SHA-2 em ARMv8 do kernel. Ajuste os parâmetros read-ahead e prefetch_cluster para o seu hardware; a verificação por bloco normalmente adiciona pouco ao custo de E/S, mas essas configurações fazem a diferença.
Introdução ao Linux (systemd, veritysetup) e Android
Em um Linux moderno com systemd, dm-verity permite uma raiz somente leitura verificada usando veritysetup (parte do cryptsetup), systemd-veritysetup.generator e systemd-veritysetup@.service. Recomenda-se incluir o Secure Boot e uma UKI (imagem unificada do kernel) assinada, embora não sejam estritamente necessários.
Preparação e particionamento recomendado
Parte de um sistema funcional e ajustado. Reserve um volume para a árvore de hash (8–10% do tamanho da raiz geralmente é suficiente) e considere separar /home e /var se precisar gravar. Um esquema típico inclui: ESP (para o bootloader), XBOOTLDR (para UKIs), raiz (com ou sem criptografia), partição VERITY e, opcionalmente, /home e /var.
Como raiz, EROFS é uma alternativa muito interessante ao ext4 ou squashfs:É somente leitura por design, com ótimo desempenho em flash/SSD, compressão lz4 por padrão e amplamente utilizado em telefones Android com dm-verity.
Arquivos que devem ser graváveis
Com root ro, alguns programas esperam escrever para /etc ou durante a inicializaçãoVocê pode movê-lo para /var/etc e criar um link simbólico para qualquer coisa que precise ser alterada (por exemplo, conexões do NetworkManager em /etc/NetworkManager/system-connections). Observe que o systemd-journald exige que /etc/machine-id exista no diretório raiz (não um link simbólico) para evitar interrupções em inicializações iniciais.
Para descobrir o que muda na execução, usar dracut-overlayroot: sobrepõe um tmpfs à raiz, e tudo o que for escrito aparece em /run/overlayroot/u. Adicione o módulo a /usr/lib/dracut/modules.d/, inclua overlayroot em dracut e defina overlayroot=1 na linha do kernel; assim, você verá o que migrar para /var.
Exemplos úteis: pacman e NetworkManager
No Arch, é conveniente Mova o banco de dados Pacman para /usr/lib/pacman para que o rootfs sempre espelhe os pacotes instalados. Em seguida, redirecione o cache para /var/lib/pacman e vincule-o. Para alterar a lista de espelhos sem alterar a raiz, mova-a para /var/etc e vincule-a mesmo assim.
Com o NetworkManager, mover conexões do sistema para /var/etc/NetworkManager e link de /etc/NetworkManager/system-connections. Isso mantém a raiz imutável e a configuração ativa onde deveria ser gravável.
Construção da verdade e teste
A partir de um live e com tudo perfeito e montado no ro, crie a árvore e o roothash com formato veritysetup: Ao ser executado, ele imprime a linha do Hash da Raiz, que você pode salvar em roothash.txt. Execute-o para testes com veritysetup, abra root-device root verity-device $(cat roothash.txt) e monte /dev/mapper/root.
Se você preferir, primeiro gera a árvore para um arquivo (verity.bin) e, em seguida, grave-o na partição VERITY. O conjunto resultante é: imagem raiz, árvore Verity e o hash raiz que você fixará na inicialização.
Configurar a linha do kernel
Adicione estes parâmetros: systemd.verity=1, roothash=contents_of_roothash.txt, systemd.verity_root_data=ROOT-PATH (ex.: LABEL=OS) e systemd.verity_root_hash=VERITY-PATH (ex.: LABEL=VERITY). Defina systemd.verity_root_options como restart-on-corruption ou panic-on-corruption para políticas mais rígidas.
Outras opções recomendadas: ro (se você não usa EROFS/squashfs), rd.emergência=reinicializar y rd.shell=0 (prevenir shells não autorizados se a inicialização falhar) e bloqueio = confidencialidade para proteger a memória do kernel do acesso.
Partições adicionais com verity
Não apenas a raiz: Você pode definir outros mapeamentos em /etc/veritytab e systemd-veritysetup@.service os montará na inicialização. Lembre-se: é mais fácil montar via RW uma partição não root, e um usuário root poderia desabilitar o Verity nessas partições, então o valor de segurança ali é menor.
Segurança: Secure Boot, UKI e módulos assinados
dm-verity não é uma solução mágica. Assine o UKI e habilite o Secure Boot com suas próprias chaves para impedir que alguém sobrescreva kernel/initramfs/cmdline (que inclui o hash raiz). Ferramentas como sbupdate-git ou sbctl ajudam a manter as imagens assinadas e a cadeia de inicialização intactas.
Se você habilitar o bloqueio do kernel ou a verificação da assinatura do módulo, Os módulos DKMS ou fora da árvore devem ser assinados ou eles não serão carregados. Considere um kernel personalizado com suporte a assinatura para seu pipeline (consulte módulos de kernel assinados).
Criptografia, TPM e medição
dm-verity protege a integridade, não confidencialidadeVocê pode deixar o root sem criptografia se ele não contiver segredos e a cadeia de inicialização estiver protegida. Se você usar arquivos-chave do root para desbloquear outros volumes, é uma boa ideia criptografá-los.
Com o TPM 2.0, systemd-cryptenroll permite chaves de ligação aos PCRs 0,1,5,7 (firmware, opções, GPT, status de inicialização segura). Adicione rd.luks.options=LUKS_UUID=tpm2-device=auto e certifique-se de incluir o suporte a TPM2 no initramfs. O systemd-boot mede o kernel.efi no PCR4, útil para invalidar chaves se o UKI ou sua linha de comando forem alterados.
Atualizações e modelos de implantação
Uma raiz somente leitura verificada Não é atualizado com o gerenciador de pacotes da maneira tradicional. O ideal é construir novas imagens com ferramentas como o projeto Yocto e publicá-los. O systemd tem systemd-sysupdate e systemd-repart para download e atualização de imagens robustas.
Outra estratégia é Esquema A/B: Você mantém duas raízes e duas veridades. Copie a raiz ativa para a raiz inativa, aplique as alterações e refaça a veridade. Alterne novamente na próxima inicialização. Se estiver usando o UKI, lembre-se de atualizar o hash da raiz na linha de comando ou reconstruir o UKI assinado.
Para persistência opcional, use OverlayFS na raiz verificada com upper em tmpfs ou disco. Você também pode passar systemd.volatile=overlay para persistência temporária. O Flatpak facilita a instalação de aplicativos em /var e /home sem precisar tocar em /.
Existem pacotes automatizados (por exemplo, verity-squash-root no AUR) que criam uma raiz squashfs e assinar o roothash com kernel e initramfs, permitindo que você escolha entre o modo persistente ou efêmero e preservando o rootfs mais recente como backup. Observação: adicionar persistência a uma raiz verificada tem casos de uso limitados; tente persistir os dados do aplicativo em partições separadas.
Android: sistema como root, AVB e sobreposições de fornecedores
Desde o Android 10, O RootFS para de ser executado no disco RAM e se integra com system.img. (sistema como root). Dispositivos com Android 10 sempre usam esse esquema e exigem um ramdisk para dm-linear. BOARD_BUILD_SYSTEM_ROOT_IMAGE está definido como falso nesta compilação para distinguir entre usar um ramdisk e ativar diretamente system.img.
O Android 10 incorpora partições dinâmicas e um init de primeiro estágio que ativa a partição lógica do sistema; o kernel não a monta mais diretamente. OTAs somente de sistema exigem um design de sistema como root, obrigatório em dispositivos Android 10.
Em nenhum A/B, mantenha a recuperação separada da inicializaçãoAo contrário de A/B, não há backup boot_a/boot_b, portanto, remover a recuperação em um sistema que não seja A/B pode deixá-lo sem o modo de recuperação se uma atualização de inicialização falhar.
O kernel monta system.img em /converity por meio de dois caminhos: vboot 1.0 (patches para o kernel analisar metadados do Android em /system e derivar parâmetros dm-verity; o cmdline inclui root=/dev/dm-0, skip_initramfs e init=/init com dm=…) ou vboot 2.0/AVB, onde o bootloader integra o libavb, lê o descritor hashtree (em vbmeta ou system), constrói os parâmetros e os passa para o kernel na cmdline, com suporte a FEC e sinalizadores como restart_on_corruption.
Com o sistema como root, não use BOARD_ROOT_EXTRA_FOLDERS Para pastas raiz específicas do dispositivo: elas desaparecerão ao atualizar um GSI. Defina montagens específicas em /mnt/vendor/ , que o fs_mgr cria automaticamente e os referencie no fstab da árvore de dispositivos.
O Android permite um sobreposição de fornecedor de /product/vendor_overlay/: init montará em /vendor os subdiretórios que atendem aos requisitos de contexto do SELinux e à existência de /vendor/ . Requer CONFIG_OVERLAY_FS=yy, em kernels mais antigos, o patch override_creds=off.
Implementação típica: instala arquivos pré-compilados em device/ / /sobreposição_do_vendedor/, adicione-os a PRODUCT_COPY_FILES com find-copy-subdir-files para $(TARGET_COPY_OUT_PRODUCT)/vendor_overlay, defina contextos em file_contexts para etc e app (por exemplo, vendor_configs_file e vendor_app_file) e permita a montagem nesses contextos em init.te. Teste com atest vfs_mgr_vendor_overlay_test em userdebug.
Solução de problemas: mensagem de corrupção dm-verity no Android
Em dispositivos com slots A/B, troque os slots ou Atualizando vbmeta/boot sem consistência com o roothash Isso pode acionar o aviso: dm-verity corruption, seu dispositivo não é confiável. Comandos como fastboot flash –disable-verity –disable-verification vbmeta vbmeta.img desabilitam a verificação, mas deixam o sistema sem nenhuma garantia de integridade.
Alguns bootloaders suportam fastboot oem disable_dm_verity e seu oposto, enable_dm_verity. Funciona em alguns modelos, mas não em outros; e pode exigir kernel/magisk com sinalizadores ajustados. Use por sua conta e risco: a atitude mais prudente é alinhar boot, vbmeta e sistema, assine ou regenere a árvore e certifique-se de que o hash raiz esperado corresponda ao configurado.
Se após o aviso você puder continuar pressionando o botão liga/desliga, o sistema inicia, mas você não tem mais uma cadeia de confiança intactaPara remover a mensagem sem sacrificar a segurança, restaure as imagens assinadas originais ou reconstrua/verifique o vbmeta com o hashtree correto, em vez de desabilitar o verity.
Plataformas i.MX e OpenWrt
No i.MX6 (por exemplo, sabresd), configurar o kernel com suporte a DM_VERITY e FEC, gere a árvore com o veritysetup, armazene o hash raiz com segurança e passe os parâmetros apropriados na linha de comando ou integre via initramfs com o systemd-veritysetup. Se você não usa o dm-crypt, não precisa do CAAM para o verity; o foco é a integridade.
No OpenWrt e em sistemas Linux embarcados com OpenEmbedded, Há esforços para integrar dm-verity e SELinux (Trabalhos Bootlin revisados com a intenção de incorporar suporte). É uma combinação natural: roteadores e equipamentos de rede se beneficiam de uma raiz imutável, verificada e protegida por MAC.
Construção manual de árvore e metadados (visão detalhada)
O cryptsetup pode gerar a árvore para você, mas se preferir entender o formato, a definição compacta da linha da tabela inclui: nome do mapeamento, dispositivo de dados, tamanhos de bloco de dados e hash, tamanho da imagem em blocos, posição inicial do hash (imagem do bloco + 8 se concatenado), hash raiz e salt. Após gerar as camadas concatenadas (de cima para baixo, excluindo a camada 0), você grava a árvore no disco.
Para embalar tudo, componha a tabela dm-verity, assine-a (típico RSA-2048) e agrupe signature+table nos metadados com um cabeçalho versionado e um número mágico. Em seguida, ele concatena a imagem do sistema, os metadados do Verity e a árvore de hash. No fstab, ele marca fs_mgr como verify e coloca a chave pública em /boot/verity_key para validar a assinatura.
Otimizar com Acelerações SHA-2 para sua CPU e ajuste o read-ahead/prefetch_cluster. Em hardware ARM, as extensões NEON SHA-2 (ARMv7) e SHA-2 (ARMv8) reduzem significativamente a sobrecarga de verificação.
Em qualquer implantação, lembre-se de que o valor do hash raiz deve ser protegido: seja compilado em um UKI assinado, na partição de inicialização assinada ou validado pelo bootloader usando AVB. Tudo depois desse ponto herda essa confiança.
Com tudo isso em vigor, o dm-verity se torna uma base sólida para sistemas imutáveis, móveis e embarcados, suportando atualizações transacionais, sobreposições de configuração e um modelo de segurança moderno que reduz a superfície de ataque e evita a persistência sem sacrificar o desempenho.


