Implementando dm-verity

Android 4.4 y versiones posteriores admiten 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 rootkits persistentes que pueden conservar 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 utilizó por última vez.

Las aplicaciones potencialmente dañinas (PHA) con privilegios de root pueden ocultarse de los programas de detección o enmascararse. El software de rooteo 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 su configuración esperada. Lo hace utilizando 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, sólo 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 obtener una descripción de esta estructura.

dm-verity-hash-tabla

Figura 1. tabla hash de dm-verity

Se incluye una clave pública en la partición de inicio, que el fabricante del dispositivo debe verificar externamente. Esa clave se utiliza 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 rooteo compromete el sistema antes de que se active 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 utilizan esa clave para verificar la firma en el gestor de arranque de primer nivel, que a su vez verifica la firma en los niveles posteriores, el gestor 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. Suponiendo 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 un período prolongado 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 procesa 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 la verificación falla, el dispositivo genera un error de E/S que indica que el bloque no se puede leer. Parecerá como si el sistema de archivos estuviera dañado, como era de esperar.

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 hacia adelante

Android 7.0 y versiones posteriores mejoran la solidez de dm-verity con corrección de errores directa (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 corruptos 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. Genera una imagen del sistema ext4.
  2. Genera un árbol hash para esa imagen.
  3. Cree una tabla dm-verity para ese árbol hash.
  4. Firme esa tabla dm-verity para generar una firma de tabla.
  5. Agrupe la firma de la tabla y la tabla dm-verity en metadatos de verdad.
  6. Concatene la imagen del sistema, los metadatos de verdad y el árbol hash.

Consulte The Chromium Projects - Arranque verificado 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, aquí se define uno compatible:

<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 de los cuales 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 manera 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 obtenga el SHA256 de ese bloque, tendrá el hash raíz del árbol.

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

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

Para generar el árbol hash, concatene los hashes de la capa 2 con los de la capa 1, los hashes 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. Separe la imagen de su 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. Concatena el nivel a tu árbol hash.
  7. Repita los pasos 2 a 6 utilizando el nivel anterior como fuente para el siguiente hasta que tenga un solo hash.

El resultado de esto es un hash único, que es su 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 es el mismo valor). Este mapeo se usa para la generación y el arranque fstab . La tabla también identifica el tamaño de los bloques y hash_start, la ubicación inicial del árbol hash (específicamente, su número de bloque desde el principio de la imagen).

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

Firmando la tabla dm-verity

Firme la tabla dm-verity para generar una firma de tabla. Al verificar una partición, primero se valida la firma de la tabla. Esto se hace con una clave en su imagen de inicio 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 firma y combinación de claves:

  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 de la entrada relevante, agregue verify a las banderas fs_mgr .

Agrupar la firma de la tabla en metadatos

Agrupe la firma de la tabla y la tabla dm-verity en metadatos de verdad. Todo el bloque de metadatos tiene versiones para que pueda ampliarse, como por ejemplo para agregar un segundo tipo de firma o cambiar algún orden.

Como comprobación 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 metadatos sin conocer el contenido de los datos en sí.

Esto asegura que no haya elegido verificar una partición no verificada. De ser 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 verdad:

<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 mesa en forma acolchada 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 relleno 0 hasta 32k de longitud 0

Optimización de dm-verity

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

  • 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.