Implementando dm-verity

Android 4.4 y versiones posteriores son compatibles con el Arranque verificado a través de la función de kernel opcional device-mapper-verity (dm-verity), que proporciona una verificación transparente de la integridad de los dispositivos de bloque. dm-verity ayuda a prevenir los rootkits persistentes que pueden retener los privilegios de root y comprometer los dispositivos. Esta función ayuda a los usuarios de Android a asegurarse de que, al iniciar un dispositivo, esté en el mismo estado que cuando se usó por última vez.

Las aplicaciones potencialmente dañinas (PHA) con privilegios de root pueden ocultarse de los programas de detección y enmascararse. El software de enraizamiento puede hacer esto porque a menudo tiene más privilegios que los detectores, lo que le permite "mentir" a los programas de detección.

La función dm-verity le permite observar un dispositivo de bloque, la capa de almacenamiento subyacente del sistema de archivos, y determinar si coincide con la configuración esperada. Lo hace usando un árbol hash criptográfico. Para cada bloque (normalmente 4k), hay un hash SHA256.

Debido a que los valores hash se almacenan en un árbol de páginas, solo se debe confiar en el hash "raíz" de nivel superior para verificar el resto del árbol. La posibilidad de modificar cualquiera de los bloques equivaldría a romper el hash criptográfico. Consulte el siguiente diagrama para ver una representación de esta estructura.

dm-verity-hash-tabla

Figura 1. Tabla hash dm-verity

Se incluye una clave pública en la partición de arranque, que debe ser verificada externamente por el fabricante del dispositivo. Esa clave se usa para verificar la firma de ese hash y confirmar que la partición del sistema del dispositivo está protegida y sin cambios.

Operación

La protección dm-verity vive en el kernel. Entonces, si el software de enraizamiento compromete el sistema antes de que aparezca el kernel, conservará ese acceso. Para mitigar este riesgo, la mayoría de los fabricantes verifican el kernel utilizando una clave grabada en el dispositivo. Esa clave no se puede cambiar una vez que el dispositivo sale de fábrica.

Los fabricantes usan esa clave para verificar la firma en el cargador de arranque de primer nivel, que a su vez verifica la firma en los niveles posteriores, el cargador de arranque de la aplicación y, finalmente, el kernel. Cada fabricante que desee aprovechar el arranque verificado debe tener un método para verificar la integridad del kernel. Asumiendo que el kernel ha sido verificado, el kernel puede mirar un dispositivo de bloque y verificarlo mientras está montado.

Una forma de verificar un dispositivo de bloque es analizar directamente su contenido y compararlo con un valor almacenado. Sin embargo, intentar verificar un dispositivo de bloque completo puede llevar mucho tiempo y consumir gran parte de la energía del dispositivo. Los dispositivos tardarían mucho en arrancar y luego se agotarían significativamente antes de su uso.

En cambio, dm-verity verifica los bloques individualmente y solo cuando se accede a cada uno. Cuando se lee en la memoria, el bloque se codifica en paralelo. Luego, el hash se verifica en el árbol. Y dado que leer el bloque es una operación tan costosa, la latencia introducida por esta verificación a nivel de bloque es comparativamente nominal.

Si falla la verificación, el dispositivo genera un error de E/S que indica que no se puede leer el bloque. Aparecerá como si el sistema de archivos se hubiera dañado, como se esperaba.

Las aplicaciones pueden optar por continuar sin los datos resultantes, como cuando esos resultados no son necesarios para la función principal de la aplicación. Sin embargo, si la aplicación no puede continuar sin los datos, fallará.

Corrección de errores de reenvío

Android 7.0 y versiones posteriores mejoran la robustez de dm-verity con corrección de errores de reenvío (FEC). La implementación de AOSP comienza con el código común de corrección de errores Reed-Solomon y aplica una técnica llamada entrelazado para reducir la sobrecarga de espacio y aumentar la cantidad de bloques dañados que se pueden recuperar. Para obtener más detalles sobre FEC, consulte Arranque verificado estrictamente aplicado con corrección de errores .

Implementación

Resumen

  1. Genere una imagen del sistema ext4.
  2. Genere un árbol hash para esa imagen.
  3. Cree una tabla dm-verity para ese árbol hash.
  4. Firme esa tabla dm-verity para producir una firma de tabla.
  5. Agrupe la firma de la tabla y la tabla dm-verity en los metadatos de verity.
  6. Concatene la imagen del sistema, los metadatos de verity y el árbol hash.

ConsulteThe Chromium Projects - Verified Boot para obtener una descripción detallada del árbol hash y la tabla dm-verity.

Generando el árbol hash

Como se describe en la introducción, el árbol hash es parte integral de dm-verity. La herramienta cryptsetup generará un árbol hash para usted. Alternativamente, uno compatible se define aquí:

<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 el hash, la imagen del sistema se divide en la capa 0 en bloques de 4k, a cada uno se le asigna un hash SHA256. La capa 1 se forma uniendo solo esos hashes SHA256 en bloques de 4k, lo que da como resultado una imagen mucho más pequeña. La capa 2 se forma de forma idéntica, con los hashes SHA256 de la capa 1.

Esto se hace hasta que los hashes SHA256 de la capa anterior puedan caber en un solo bloque. Cuando obtienes el SHA256 de ese bloque, tienes el hash raíz del árbol.

El tamaño del árbol hash (y el uso de espacio en disco correspondiente) varía según el tamaño de la partición verificada. En la práctica, el tamaño de los árboles de hash tiende a ser pequeño, a menudo de menos de 30 MB.

Si tiene un bloque en una capa que no está completamente lleno de forma natural con los valores hash de la capa anterior, debe rellenarlo con ceros para lograr los 4k esperados. Esto le permite saber que el árbol hash no se eliminó y, en cambio, se completó con datos en blanco.

Para generar el árbol hash, concatene los hash de la capa 2 con los de la capa 1, los hash de la capa 3 con los de la capa 2, y así sucesivamente. Escriba todo esto en el disco. Tenga en cuenta que esto no hace referencia a la capa 0 del hash raíz.

En resumen, el algoritmo general para construir el árbol hash es el siguiente:

  1. Elija una sal aleatoria (codificación hexadecimal).
  2. Separa la imagen de tu sistema en bloques de 4k.
  3. Para cada bloque, obtenga su hash SHA256 (salado).
  4. Concatenar estos hashes para formar un nivel
  5. Rellene el nivel con 0 hasta un límite de bloque de 4k.
  6. Concatene el nivel a su árbol hash.
  7. Repita los pasos 2 a 6 utilizando el nivel anterior como fuente para el siguiente hasta que tenga un único hash.

El resultado de esto es un solo hash, que es tu hash raíz. Esto y su sal se utilizan durante la construcción de su tabla de mapeo dm-verity.

Construyendo la tabla de mapeo dm-verity

Cree la tabla de mapeo dm-verity, que identifica el dispositivo de bloque (o destino) para el kernel y la ubicación del árbol hash (que tiene el mismo valor). Este mapeo se usa para generar y arrancar fstab . La tabla también identifica el tamaño de los bloques y el hash_start, la ubicación de inicio del árbol hash (específicamente, su número de bloque desde el comienzo de la imagen).

Consulte cryptsetup para obtener una descripción detallada de los campos de la tabla de asignación de objetivos de verity.

Firmando la tabla dm-verity

Firme la tabla dm-verity para producir una firma de tabla. Al verificar una partición, la firma de la tabla se valida primero. Esto se hace contra una clave en su imagen de arranque en una ubicación fija. Las claves generalmente se incluyen en los sistemas de compilación de los fabricantes para su inclusión automática en dispositivos en una ubicación fija.

Para verificar la partición con esta combinación de firma y clave:

  1. Agregue una clave RSA-2048 en formato compatible con libmincrypt a la partición /boot en /verity_key . Identifique la ubicación de la clave utilizada para verificar el árbol hash.
  2. En el fstab para la entrada relevante, agregue verify a los indicadores fs_mgr .

Empaquetar la firma de la tabla en metadatos

Agrupe la firma de la tabla y la tabla dm-verity en los metadatos de verity. Todo el bloque de metadatos está versionado, por lo que puede ampliarse, por ejemplo, para agregar un segundo tipo de firma o cambiar algún orden.

Como control de cordura, se asocia un número mágico con cada conjunto de metadatos de la tabla que ayuda a identificar la tabla. Dado que la longitud se incluye en el encabezado de la imagen del sistema ext4, esto proporciona una forma de buscar los metadatos sin conocer el contenido de los datos en sí.

Esto asegura que no haya elegido verificar una partición no verificada. Si es así, la ausencia de este número mágico detendrá el proceso de verificación. Este número se parece a:
0xb001b001

Los valores de bytes en hexadecimal son:

  • primer byte = b0
  • segundo byte = 01
  • tercer byte = b0
  • cuarto byte = 01

El siguiente diagrama muestra el desglose de los metadatos de verity:

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

Y esta tabla describe esos campos de metadatos.

Tabla 1. Campos de metadatos de Verity

Campo Objetivo Tamaño Valor
número mágico utilizado por fs_mgr como control de cordura 4 bytes 0xb001b001
versión utilizado para versionar el bloque de metadatos 4 bytes actualmente 0
firma la firma de la tabla en forma rellenada PKCS1.5 256 bytes
longitud de la mesa la longitud de la tabla dm-verity en bytes 4 bytes
mesa la tabla dm-verity descrita anteriormente bytes de longitud de tabla
relleno esta estructura tiene un relleno de 0 hasta 32k de longitud 0

Optimización de dm-verity

Para obtener el mejor rendimiento de dm-verity, debe:

  • En el kernel, active NEON SHA-2 para ARMv7 y las extensiones SHA-2 para ARMv8.
  • Experimente con diferentes configuraciones de lectura anticipada y prefetch_cluster para encontrar la mejor configuración para su dispositivo.