Cómo usar dm-verity en Linux: guía completa y práctica

  • dm-verity verifica bloques al vuelo con un árbol de hashes raíz firmado, anclando la cadena de confianza del arranque.
  • Su despliegue moderno combina veritysetup, systemd-veritysetup, Secure Boot y UKI para proteger kernel, initramfs y cmdline.
  • Android usa system-as-root y AVB para pasar parámetros dm-verity; FEC y políticas de reacción refuerzan la robustez.
  • La raíz inmutable exige separar datos escribibles (/var, /home) y planificar actualizaciones mediante imágenes o esquema A/B.

dm-verity en Linux

Si te preocupa la integridad de tu sistema, dm-verity es una de las piezas clave del ecosistema Linux para arrancar con garantías y detectar manipulaciones en el almacenamiento. Nació como parte del device-mapper del kernel y hoy es la base de arranques verificados en Android, OpenWrt y distribuciones que buscan seguridad reforzada.

Lejos de ser un concepto abstracto, dm-verity se configura y usa con herramientas reales como veritysetup y systemd-veritysetup, valida bloques al vuelo mediante árboles de hashes y puede reaccionar a corrupción con políticas que van desde registrar el evento hasta reiniciar o bloquear el sistema. Vamos a verlo a fondo y sin dejar flecos.

Qué es dm-verity y por qué te interesa

Verificación de integridad con dm-verity

dm-verity es un target de device-mapper en el kernel que verifica la integridad de un dispositivo de bloques a medida que se leen los datos. Funciona calculando y comprobando hashes de cada bloque (habitualmente 4K) frente a un árbol de verificación precomputado, normalmente con SHA-256.

Este diseño permite que los archivos no puedan modificarse silenciosamente entre reinicios ni durante la ejecución. Es clave para extender la cadena de confianza del arranque hacia el sistema operativo, limitar persistencia de malware, reforzar políticas de seguridad y dar garantías a mecanismos de cifrado y MAC durante el boot.

En Android (desde la 4.4) y Linux en general, la confianza se ancla en el hash raíz del árbol, que está firmado y validado con una clave pública que reside en una ubicación protegida (por ejemplo en la partición de arranque o en un UKI firmado con Secure Boot). Romper cualquier bloque requeriría quebrar el hash criptográfico subyacente.

La verificación se hace por bloque y bajo demanda: la latencia añadida es mínima comparada con el coste de E/S. Si una comprobación falla, el kernel devuelve un error de E/S y el sistema de ficheros parece dañado, que es lo esperado cuando los datos no son confiables. Las apps pueden decidir si continúan o no según su tolerancia a errores.

Cómo funciona internamente el árbol de verificación

El árbol de verificación se construye por capas. La capa 0 son los datos crudos del dispositivo, divididos en bloques de 4K; de cada bloque se calcula un hash SHA-256 (con sal). Estos hashes se concatenan para formar la capa 1. A su vez, la capa 1 se agrupa en bloques y se rehasea para formar la capa 2, y así sucesivamente hasta que cabe todo en un único bloque: ese bloque, al hashearse, produce el hash raíz.

Si alguna capa no completa exactamente un bloque, se rellena con ceros hasta alcanzar 4K para no dejar ambigüedades. El tamaño total del árbol depende del tamaño de la partición verificada; en la práctica, suele quedar por debajo de unos 30 MB para particiones de sistema habituales.

El proceso general es: elige una sal aleatoria, trocea a 4K, calcula SHA-256 con sal por bloque, concatena para formar niveles, rellena con ceros a frontera de bloque y repite con el nivel anterior hasta quedarse con un único hash raíz. Ese hash raíz, junto con la sal empleada, alimenta la tabla de dm-verity y la firma.

Versiones de formato en disco y algoritmo

El formato de los bloques de hash en disco tiene una versión. La versión 0 fue la original usada en Chromium OS: la sal se añade al final al hashear, los digests se almacenan de forma continua y el resto del bloque se rellena con ceros.

La versión 1 es la recomendada para dispositivos nuevos: la sal se antepone al hashear y cada digest se rellena con ceros hasta potencias de dos, mejorando alineamiento y robustez. La tabla dm-verity también especifica el algoritmo (por ejemplo, sha1 o sha256), aunque para seguridad actual se emplea sha256.

Tabla dm-verity y parámetros esenciales

La tabla de destino dm-verity describe dónde están los datos, dónde está el árbol de hashes y cómo verificar. Campos típicos de la tabla:

  • dev: dispositivo con los datos a verificar (ruta tipo /dev/sdXN o mayor:menor).
  • hash_dev: dispositivo con el árbol de hashes (puede ser el mismo; si lo es, hash_start debe quedar fuera del rango verificado).
  • data_block_size: tamaño de bloque de datos en bytes (p.ej. 4096).
  • hash_block_size: tamaño de bloque de hash en bytes.
  • num_data_blocks: número de bloques de datos verificables.
  • hash_start_block: offset (en bloques de hash_block_size) hasta el bloque raíz del árbol.
  • algorithm: algoritmo de hash (por ejemplo, sha256).
  • digest: codificación hexadecimal del hash del bloque raíz (incluyendo sal según versión de formato); este valor es el que se debe confiar.
  • salt: sal en hexadecimal.

Además, existen parámetros opcionales muy útiles para ajustar el comportamiento:

  • ignore_corruption: registra bloques corruptos, pero permite continuar leyendo.
  • restart_on_corruption: reinicia al detectar corrupción (no compatible con ignore_corruption y requiere soporte en espacio de usuario para evitar bucles).
  • panic_on_corruption: hace panic al detectar corrupción (no compatible con las anteriores).
  • restart_on_error y panic_on_error: mismas reacciones pero para errores de E/S.
  • ignore_zero_blocks: no verifica bloques que se esperan como ceros y devuelve ceros.
  • use_fec_from_device + fec_roots + fec_blocks + fec_start: habilita FEC (Reed–Solomon) para recuperar datos cuando falle la verificación; las áreas de datos, hash y FEC no deben solaparse y los tamaños de bloque deben coincidir.
  • check_at_most_once: verifica cada bloque de datos solo la primera vez que se lee (reduce overhead a costa de seguridad en ataques en vivo).
  • root_hash_sig_key_desc: referencia a una clave en el keyring para validar una firma PKCS7 del hash raíz en la creación del mapeo (requiere la configuración de kernel adecuada y keyrings de confianza).
  • try_verify_in_tasklet: si los hashes están en caché y el tamaño de E/S lo permite, verifica en bottom-half para reducir latencia; se ajusta con /sys/module/dm_verity/parameters/use_bh_bytes por clase de E/S.

Firma, metadatos y anclaje de confianza

Para que dm-verity sea fiable, el hash raíz debe confiarse y, normalmente, firmarse. En Android clásico se incluye una clave pública en la partición de inicio que el fabricante verifica externamente; con ella se valida la firma del hash raíz y se garantiza que la partición de sistema no ha sido alterada.

Los metadatos de verity agregan estructura y control de versiones. El bloque de metadatos incluye un número mágico 0xb001b001 (bytes b0 01 b0 01), versión (actualmente 0), la firma de la tabla en PKCS1.5 (típicamente 256 bytes para RSA-2048), la longitud de la tabla, la tabla misma y relleno con ceros hasta 32K.

En implementaciones Android, la verificación se apoya en fs_mgr y fstab: añadiendo la marca de verificación en la entrada correspondiente y colocando la clave en /boot/verity_key. Si el número mágico no aparece donde toca, la verificación se detiene para evitar verificar lo que no corresponde.

Operación del arranque verificado

La protección vive en el kernel: si se compromete antes de que el kernel arranque, el atacante retiene el control. Por eso los fabricantes suelen validar firmemente cada etapa: una clave quemada en el dispositivo verifica el primer bootloader, que verifica el siguiente, el bootloader de apps y, por último, el kernel.

Con el kernel verificado, dm-verity se activa al montar el dispositivo de bloques verificado. En lugar de hashear todo el dispositivo (que sería lento y gastaría energía), se verifica bloque a bloque cuando se accede. Un fallo provoca error de E/S, y servicios y apps reaccionan según su tolerancia: seguir sin esos datos o fallar con contundencia.

Corrección de errores por anticipado (FEC)

Desde Android 7.0, se incorpora FEC (Reed–Solomon) con técnicas de entrelazado para reducir espacio y aumentar la capacidad de recuperar bloques dañados. Esto convive con dm-verity: si una verificación falla, el subsistema puede intentar corrección antes de declararlo irrecuperable.

Rendimiento y optimización

Para reducir impacto: activa aceleración de SHA-2 por NEON en ARMv7 y extensiones SHA-2 en ARMv8 desde el kernel. Ajusta parámetros de read-ahead y prefetch_cluster para tu hardware; la verificación por bloque suele añadir poco frente al coste de E/S, pero estos ajustes marcan diferencias.

Puesta en marcha en Linux (systemd, veritysetup) y Android

Configurar dm-verity en Linux y Android

En un Linux moderno con systemd, dm-verity permite un root de solo lectura verificado usando veritysetup (parte de cryptsetup), systemd-veritysetup.generator y systemd-veritysetup@.service. Es recomendable acompañarlo de Secure Boot y un UKI (unified kernel image) firmado, aunque no son estrictamente obligatorios.

Preparación y particionado recomendado

Parte de un sistema funcional y ajustado. Reserva un volumen para el árbol de hashes (8–10% del tamaño de la raíz suele bastar) y valora separar /home y /var si necesitas escritura. Un esquema típico incluye: ESP (para el bootloader), XBOOTLDR (para UKIs), raíz (con o sin cifrado), partición VERITY, y opcionalmente /home y /var.

Como raíz, EROFS es una alternativa muy interesante a ext4 o squashfs: es de solo lectura por diseño, con rendimiento muy bueno en flash/SSD, compresión lz4 por defecto y ampliamente usado en móviles Android con dm-verity.

Ficheros que deben ser escribibles

Con raíz ro, algunos programas esperan escribir en /etc o durante el init. Puedes mover a /var/etc y enlazar simbólicamente lo que deba cambiar (p.ej. conexiones de NetworkManager en /etc/NetworkManager/system-connections). Ten en cuenta que systemd-journald exige que /etc/machine-id exista en la raíz (no un symlink) para no romper arranques tempranos.

Para descubrir qué cambia en ejecución, usa dracut-overlayroot: superpone un tmpfs sobre la raíz, y todo lo escrito aparece en /run/overlayroot/u. Añade el módulo en /usr/lib/dracut/modules.d/, incluye overlayroot en dracut y pon overlayroot=1 en la línea de kernel; así verás qué migrar a /var.

Ejemplos útiles: pacman y NetworkManager

En Arch, conviene mover la base de datos de pacman a /usr/lib/pacman para que el rootfs refleje siempre los paquetes instalados. Después, redirige la caché a /var/lib/pacman y enlaza. Para cambiar mirrorlist sin tocar la raíz, muévela a /var/etc y enlaza igual.

Con NetworkManager, traslada system-connections a /var/etc/NetworkManager y enlaza desde /etc/NetworkManager/system-connections. Así mantienes raíz inmutable y configuración viva donde debe ser escribible.

Construcción del verity y prueba

Desde un live y con todo perfecto y montado en ro, crea el árbol y roothash con veritysetup format: al ejecutarse, imprime la línea del Root Hash que puedes guardar en roothash.txt. Abre para prueba con veritysetup open root-device root verity-device $(cat roothash.txt) y monta /dev/mapper/root.

Si prefieres, genera primero el árbol a un fichero (verity.bin) y después escríbelo a la partición VERITY. El conjunto resultante son: imagen raíz, árbol verity y el roothash que anclarás en el arranque.

Configurar la línea de kernel

Añade estos parámetros: systemd.verity=1, roothash=contenido_de_roothash.txt, systemd.verity_root_data=RUTA-RAIZ (por ejemplo LABEL=OS) y systemd.verity_root_hash=RUTA-VERITY (por ejemplo LABEL=VERITY). Ajusta systemd.verity_root_options a restart-on-corruption o panic-on-corruption para políticas estrictas.

Otras opciones recomendables: ro (si no usas EROFS/squashfs), rd.emergency=reboot y rd.shell=0 (evitar shells no autorizadas si falla el arranque), y lockdown=confidenciality para proteger memoria del kernel frente a accesos.

Particiones adicionales con verity

No solo la raíz: puedes definir otros mapeos en /etc/veritytab y systemd-veritysetup@.service los ensamblará al boot. Recuerda: es más fácil remontar en rw una partición no root y un usuario con root podría deshabilitar verity en esas particiones, así que el valor de seguridad ahí es menor.

Seguridad: Secure Boot, UKI y módulos firmados

dm-verity no es bala de plata. Firma el UKI y habilita Secure Boot con claves propias para evitar que alguien sustituya kernel/initramfs/cmdline (que incluyen el roothash). Herramientas como sbupdate-git o sbctl ayudan a mantener imágenes firmadas y la cadena de arranque íntegra.

Si activas kernel lockdown o verificación de firmas de módulos, los módulos DKMS u out-of-tree deben firmarse o no cargarán. Valora un kernel personalizado con soporte de firma para tu flujo (véase módulos de kernel firmados).

Cifrado, TPM y medición

dm-verity protege integridad, no confidencialidad. Puedes dejar la raíz sin cifrar si no contiene secretos y la cadena de arranque está protegida. Si usas keyfiles desde la raíz para desbloquear otros volúmenes, entonces sí conviene cifrarla.

Con TPM 2.0, systemd-cryptenroll permite enlazar claves a PCRs 0,1,5,7 (firmware, opciones, GPT, estado de secure boot). Añade rd.luks.options=UUID_de_LUKS=tpm2-device=auto y asegúrate de incluir soporte TPM2 en el initramfs. systemd-boot mide el kernel.efi en PCR4, útil para invalidar claves si cambia el UKI o su cmdline.

Actualizaciones y modelos de despliegue

Un root de solo lectura verificado no se actualiza con el gestor de paquetes de forma tradicional. Lo ideal es construir imágenes nuevas con herramientas como el proyecto Yocto y publicarlas. systemd tiene systemd-sysupdate y systemd-repart para bajar y flashear imágenes de forma robusta.

Otra estrategia es esquema A/B: mantienes dos raíces y dos verity. Copias la activa a la inactiva, aplicas cambios y rehaces verity. En el siguiente arranque conmutas. Si usas UKI, recuerda actualizar el roothash en la cmdline o reconstruir el UKI firmado.

Para persistencia opcional, usa OverlayFS sobre la raíz verificada con upper en tmpfs o disco. También puedes pasar systemd.volatile=overlay para persistencia temporal. Flatpak facilita instalar apps en /var y /home sin tocar /.

Hay paquetes que automatizan (p.ej. verity-squash-root en AUR) que construyen un root squashfs y firman el roothash con kernel e initramfs, permitiendo elegir entre modo persistente u efímero y conservar el último rootfs como backup. Ojo: añadir persistencia a una raíz verificada tiene casos de uso estrechos; intenta persistir datos de apps en particiones separadas.

Android: system-as-root, AVB y overlays de vendor

Desde Android 10, el rootfs deja de ir en ramdisk y se integra con system.img (system-as-root). Dispositivos que lanzan con Android 10 usan siempre este esquema y necesitan ramdisk para dm-linear. BOARD_BUILD_SYSTEM_ROOT_IMAGE se configura en false en esta generación para distinguir uso de ramdisk frente a activar system.img directamente.

Android 10 incorpora particiones dinámicas y un init de primera etapa que activa la partición de sistema lógica; el kernel ya no la monta directamente. Las OTA solo-sistema requieren un diseño system-as-root, obligatorio en dispositivos con Android 10.

En no A/B, mantén recovery separado de boot. A diferencia de A/B, no hay respaldo boot_a/boot_b, así que eliminar recovery en no A/B puede dejarte sin modo recuperación si falla una actualización de boot.

El kernel monta system.img en / con verity mediante dos vías: vboot 1.0 (parches para que el kernel analice metadatos Android en /system y derive parámetros dm-verity; la cmdline incluye root=/dev/dm-0, skip_initramfs e init=/init con dm=…) o vboot 2.0/AVB, donde el bootloader integra libavb, lee el descriptor hashtree (en vbmeta o system), construye los parámetros y los pasa al kernel en la cmdline, con soporte de FEC e indicadores como restart_on_corruption.

Con system-as-root, no uses BOARD_ROOT_EXTRA_FOLDERS para carpetas raíz específicas del dispositivo: desaparecerán al flashear una GSI. Define montajes específicos bajo /mnt/vendor/<punto>, que fs_mgr crea automáticamente, y refiérelos en el fstab del árbol de dispositivo.

Android permite una superposición de vendor desde /product/vendor_overlay/<versión>: init montará en /vendor los subdirectorios que cumplan requisitos de contexto SELinux y existencia de /vendor/<overlay_dir>. Requiere CONFIG_OVERLAY_FS=y y, en kernels antiguos, el parche de override_creds=off.

Implementación típica: instala archivos precompilados en device/<vendor>/<target>/vendor_overlay/<versión>, añádelos a PRODUCT_COPY_FILES con find-copy-subdir-files hacia $(TARGET_COPY_OUT_PRODUCT)/vendor_overlay, define contextos en file_contexts para etc y app (por ejemplo vendor_configs_file y vendor_app_file) y permite en init.te mounton sobre esos contextos. Prueba con atest vfs_mgr_vendor_overlay_test en userdebug.

Solución de problemas: mensaje de dm-verity corruption en Android

En dispositivos con slots A/B, cambiar de slot o flashear vbmeta/boot sin coherencia con el roothash puede disparar el aviso: dm-verity corruption, tu dispositivo no es de confianza. Comandos como fastboot flash –disable-verity –disable-verification vbmeta vbmeta.img desactivan verificación, pero dejan el sistema sin garantías de integridad.

Algunos bootloaders admiten fastboot oem disable_dm_verity y su opuesto enable_dm_verity. En ciertos modelos funciona, en otros no; y puede requerir kernel/magisk con flags ajustadas. Úsalo bajo tu responsabilidad: lo prudente es alinear boot, vbmeta y system, firmar o regenerar el árbol y asegurarte de que el hash raíz esperado coincide con el configurado.

Si tras el aviso puedes continuar pulsando encendido, el sistema arranca, pero ya no tienes cadena de confianza intacta. Para eliminar el mensaje sin sacrificar seguridad, reestablece imágenes originales firmadas o reconstruye/verifica vbmeta con el hashtree correcto, en vez de desactivar verity.

Plataformas i.MX y OpenWrt

En i.MX6 (por ejemplo sabresd), configura el kernel con DM_VERITY y soporte FEC, genera el árbol con veritysetup, guarda el roothash de forma confiable y pasa los parámetros adecuados en la cmdline o integra via initramfs con systemd-veritysetup. Si no usas dm-crypt, no necesitas CAAM para verity; el foco está en integridad.

En OpenWrt y en sistemas Linux embebido con OpenEmbedded, existen esfuerzos para integrar dm-verity y SELinux (trabajos de Bootlin revisados con intención de incorporar soporte). Es un encaje natural: routers y equipos de red se benefician de raíz inmutable, verificada y endurecida con MAC.

Construcción manual del árbol y metadatos (visión detallada)

cryptsetup puede generarte el árbol, pero si prefieres entender el formato, la definición compacta de la línea de tabla incluye: nombre del mapeo, dispositivo de datos, tamaños de bloque de datos y hash, tamaño de imagen en bloques, posición del hash_start (imagen en bloques + 8 si concatenas), root hash y sal. Tras generar las capas concatenadas (de arriba a abajo, excluyendo la capa 0), escribes el árbol en disco.

Para empaquetar todo, compón la tabla dm-verity, fírmala (RSA-2048 típica) y agrupa firma+tabla en metadatos con cabecera versionada y número mágico. Después, concatena imagen de sistema, metadatos verity y árbol de hash. En fstab, marca fs_mgr con verify y coloca la clave pública en /boot/verity_key para validar la firma.

Optimiza con aceleraciones SHA-2 de tu CPU y ajusta read-ahead/prefetch_cluster. En hardware ARM, NEON SHA-2 (ARMv7) y extensiones SHA-2 (ARMv8) reducen significativamente la carga de verificación.

En cualquier despliegue, recuerda que el valor del hash raíz debe estar protegido: ya sea compilado en un UKI firmado, en la partición de boot firmada o validado por el bootloader con AVB. Todo lo que ocurra después de ese punto hereda esa confianza.

Con todo lo anterior en su sitio, dm-verity se convierte en un cimiento sólido para sistemas inmutables, móviles y embebidos, compatible con actualizaciones transaccionales, overlays de configuración y un modelo de seguridad moderno que reduce la superficie de ataque y evita persistencia sin sacrificar rendimiento.

qué es el proyecto Yocto
Artículo relacionado:
Qué es el Proyecto Yocto: guía completa para embebidos