Implementa dm-verity

Android 4.4 y versiones posteriores admiten el inicio verificado a través de la función opcional del kernel device-mapper-verity (dm-verity), que proporciona una verificación de integridad transparente de los dispositivos de almacenamiento en bloque. dm-verity ayuda a evitar rootkits persistentes que pueden conservar privilegios de administrador y vulnerar dispositivos. Esta función ayuda a los usuarios de Android a asegurarse de que, cuando inician un dispositivo, este se encuentre en el mismo estado que cuando se usó por última vez.

Las aplicaciones potencialmente dañinas (APD) con privilegios de administrador pueden ocultarse de de detección de intrusiones y se enmascaran. El software de acceso raíz 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 permite observar un dispositivo de almacenamiento en bloques, el del sistema de archivos y determinará si coincide con configuración. Para ello, usa un árbol de hash criptográfico. Por cada bloque (por lo general, 4K), hay un hash SHA256.

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

tabla-hash-dm-verity

Figura 1: Tabla de hash de dm-verity

Se incluye una clave pública en la partición de inicio, que el fabricante del dispositivo debe verificar de forma externa. 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 de dm-verity se encuentra en el kernel. Por lo tanto, si el software de acceso raíz vulnera el sistema antes de que se inicie el kernel, retiene ese acceso. Para mitigar este riesgo, la mayoría de los fabricantes verifican el kernel con una clave quemada 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 primer nivel que, a su vez, verifica la firma en los niveles posteriores, el de la app y, finalmente, el kernel. Cada fabricante que desee para aprovechar los beneficios verificados boot debe tener un método para verificar la integridad del kernel. Si se supone que se verificó el kernel, este puede ver un dispositivo de almacenamiento en bloques y verificarlo a medida que se activa.

Una forma de verificar un dispositivo de almacenamiento en bloques es generar un hash directamente en su contenido y compararlo un valor almacenado. Sin embargo, intentar verificar un dispositivo de almacenamiento en bloques completo puede llevar un período prolongado y consumir mucha energía. Los dispositivos tardarían durante períodos extensos para que se inicien y se desvíen de manera significativa antes de usarlos.

En cambio, dm-verity verifica los bloques individualmente y solo cuando cada uno a las que se accede. Cuando se lee en la memoria, el bloque genera un hash en paralelo. El hash es verificamos el árbol. Y, como leer el bloque es una operación tan costosa, la latencia que introduce esta verificación a nivel del bloque es comparativamente nominal.

Si falla la verificación, el dispositivo generará un error de E/S que indicará el bloqueo. no se pueden leer. Parece que el sistema de archivos se dañó, como se espera.

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

Corrección de errores por anticipado

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

Implementación

Resumen

  1. Genera una imagen del sistema ext4.
  2. Genera un árbol de hash para esa imagen.
  3. Crea una tabla de dm-verity para ese árbol de hash.
  4. Firma la tabla de dm-verity para generar una tabla. firma.
  5. Agrupa la firma de la tabla y la tabla de dm-verity en metadatos de verity.
  6. Concatena la imagen del sistema, los metadatos de verity y el árbol de hash.

Consulta el artículo Proyectos de Chromium: Inicio verificado para obtener una descripción detallada del árbol de hash y de la tabla dm-verity.

Genera el árbol de hash

Como se describe en la introducción, el árbol de hash es fundamental para dm-verity. La herramienta cryptsetup genera un árbol de hash por ti. Como alternativa, 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 valores hash SHA256 en bloques de 4K, lo que genera 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 valores hash 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 de hash (y el uso correspondiente del espacio en el disco) varía según el tamaño de la partición verificada. En la práctica, el tamaño de los árboles de hash suele ser pequeño, a menudo, de menos de 30 MB.

Si tienes un bloque en una capa que no se llena completamente de forma natural con hash de la capa anterior, debes rellenarla con ceros para lograr se espera 4K. Esto te permite saber que el árbol de hash no se quitó y, en su lugar, se completó con datos en blanco.

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

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

  1. Elige una sal aleatoria (codificación hexadecimal).
  2. Divide la imagen de sistema en bloques de 4,000.
  3. Para cada bloque, obtén su hash SHA256 (con sal).
  4. Concatena estos hashes para formar un nivel
  5. Agrega ceros al nivel hasta alcanzar un límite de bloque de 4K.
  6. Concatena el nivel a tu árbol de hash.
  7. Repite los pasos 2 a 6 con el nivel anterior como fuente para el siguiente solo tienes un hash.

El resultado es un solo hash, que es tu hash raíz. Esto y tu sal se utilizarán durante la creación de la tabla de asignación de dm-verity.

Compila la tabla de asignación de dm-verity

Compila la tabla de asignación de dm-verity, que identifica el dispositivo de almacenamiento en bloques (o destino) para el kernel y la ubicación del árbol de hash (que es el mismo valor). Esta la asignación se usa para la generación y el inicio de fstab. La tabla también identifica el tamaño de los bloques y hash_start, la ubicación inicial del árbol de hash (específicamente, el número de bloque desde el principio de la imagen).

Consulta cryptsetup para obtener una descripción detallada de los campos de la tabla de asignación de destino de Verity.

Firma la tabla dm-verity

Firma la tabla dm-verity para generar una firma de tabla. Cuando se verifica una partición, primero se valida la firma de la tabla. Esto se hace con una clave en tu imagen de arranque en una ubicación fija. Por lo general, las claves se incluyen de los fabricantes crear sistemas para la inclusión automática en los dispositivos en una ubicación ubicación.

Para verificar la partición con esta firma y combinación de claves, haz lo siguiente:

  1. Agrega una clave RSA-2048 en formato compatible con libmincrypt a la partición /boot en /verity_key. Identifica la ubicación de la clave que se usa para la verificación el árbol de hash.
  2. En el archivo fstab de la entrada relevante, agrega verify a las marcas fs_mgr.

Empaqueta la firma de la tabla en metadatos

Agrupa la firma de la tabla y la tabla dm-verity en metadatos de verity. Todo el de metadatos tiene un control de versiones para que se extienda, por ejemplo, para agregar tipo de firma o cambiar algún orden.

Como comprobación de estado, se asocia un número mágico con cada conjunto de metadatos de la tabla. que ayuda a identificar la tabla. Debido a 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.

Esto garantiza que no hayas 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 0xb001b001

Los valores de bytes en hexadecimal son los siguientes:

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

En el siguiente diagrama, se 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 Propósito Tamaño Valor
número mágico utilizado por fs_mgr como comprobación de estado 4 bytes 0xb001b001
version que se usa para crear versiones del bloque de metadatos 4 bytes actualmente 0
firma la firma de la tabla en formato PKCS1.5 relleno 256 bytes
longitud de la tabla la longitud de la tabla dm-verity en bytes 4 bytes
mesa la tabla dm-verity que se describió antes bytes de longitud de la tabla
relleno Esta estructura tiene relleno de 0 hasta 32,000 de longitud. 0

Optimiza dm-verity

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

  • En el kernel, activa NEON SHA-2 para ARMv7 y SHA-2 para ARMv8.
  • Experimenta con diferentes parámetros de configuración de read-ahead y prefetch_cluster para encontrar la mejor configuración para tu dispositivo.