Implementazione di dm-verity

Android 4.4 e versioni successive supportano l'avvio verificato tramite la funzionalità del kernel opzionale device-mapper-verity (dm-verity), che fornisce un controllo trasparente dell'integrità dei dispositivi a blocchi. dm-verity aiuta a prevenire i rootkit persistenti che possono mantenere i privilegi di root e compromettere i dispositivi. Questa funzione aiuta gli utenti Android ad essere sicuri che all'avvio di un dispositivo sia nello stesso stato dell'ultimo utilizzo.

Le applicazioni potenzialmente dannose (PHA) con privilegi di root possono nascondersi dai programmi di rilevamento e mascherarsi in altro modo. Il software di rooting può farlo perché spesso è più privilegiato dei rilevatori, consentendo al software di "mentire" ai programmi di rilevamento.

La funzione dm-verity consente di esaminare un dispositivo a blocchi, il livello di archiviazione sottostante del file system e determinare se corrisponde alla configurazione prevista. Lo fa usando un albero di hash crittografico. Per ogni blocco (tipicamente 4k), c'è un hash SHA256.

Poiché i valori hash sono archiviati in un albero di pagine, solo l'hash "root" di livello superiore deve essere considerato attendibile per verificare il resto dell'albero. La possibilità di modificare uno qualsiasi dei blocchi equivarrebbe a rompere l'hash crittografico. Vedere il diagramma seguente per una rappresentazione di questa struttura.

dm-verity-hash-table

Figura 1. Tabella hash dm-verity

Una chiave pubblica è inclusa nella partizione di avvio, che deve essere verificata esternamente dal produttore del dispositivo. Quella chiave viene utilizzata per verificare la firma per quell'hash e confermare che la partizione di sistema del dispositivo è protetta e invariata.

Operazione

La protezione dm-verity risiede nel kernel. Quindi, se il software di rooting compromette il sistema prima che venga attivato il kernel, manterrà quell'accesso. Per mitigare questo rischio, la maggior parte dei produttori verifica il kernel utilizzando una chiave masterizzata nel dispositivo. Quella chiave non è modificabile una volta che il dispositivo lascia la fabbrica.

I produttori utilizzano quella chiave per verificare la firma sul bootloader di primo livello, che a sua volta verifica la firma sui livelli successivi, il bootloader dell'applicazione ed eventualmente il kernel. Ogni produttore che desideri sfruttare l' avvio verificato dovrebbe disporre di un metodo per verificare l'integrità del kernel. Supponendo che il kernel sia stato verificato, il kernel può guardare un dispositivo a blocchi e verificarlo mentre è montato.

Un modo per verificare un dispositivo a blocchi è eseguire direttamente l'hashing del suo contenuto e confrontarlo con un valore memorizzato. Tuttavia, il tentativo di verificare un intero dispositivo a blocchi può richiedere molto tempo e consumare gran parte dell'energia di un dispositivo. I dispositivi impiegherebbero lunghi periodi per avviarsi e quindi si sarebbero scaricati in modo significativo prima dell'uso.

Invece, dm-verity verifica i blocchi individualmente e solo quando si accede a ciascuno di essi. Quando viene letto in memoria, il blocco viene sottoposto a hash in parallelo. L'hash viene quindi verificato sull'albero. E poiché la lettura del blocco è un'operazione così costosa, la latenza introdotta da questa verifica a livello di blocco è relativamente nominale.

Se la verifica non riesce, il dispositivo genera un errore I/O che indica che il blocco non può essere letto. Sembrerà come se il filesystem sia stato danneggiato, come previsto.

Le applicazioni possono scegliere di procedere senza i dati risultanti, ad esempio quando tali risultati non sono richiesti per la funzione principale dell'applicazione. Tuttavia, se l'applicazione non può continuare senza i dati, non riuscirà.

Correzione degli errori in avanti

Android 7.0 e versioni successive migliorano la robustezza di dm-verity con la correzione degli errori in avanti (FEC). L'implementazione di AOSP inizia con il comune codice di correzione degli errori Reed-Solomon e applica una tecnica chiamata interleaving per ridurre l'overhead di spazio e aumentare il numero di blocchi danneggiati che possono essere recuperati. Per maggiori dettagli su FEC, vedere Avvio verificato rigorosamente applicato con correzione degli errori .

Implementazione

Riepilogo

  1. Genera un'immagine di sistema ext4.
  2. Genera un albero hash per quell'immagine.
  3. Costruisci una tabella dm-verity per quell'albero hash.
  4. Firma quella tabella dm-verity per produrre una firma della tabella.
  5. Raggruppa la firma della tabella e la tabella dm-verity nei metadati di verità.
  6. Concatena l'immagine di sistema, i metadati di verità e l'albero hash.

VediThe Chromium Projects - Verified Boot per una descrizione dettagliata dell'albero hash e della tabella dm-verity.

Generazione dell'albero hash

Come descritto nell'introduzione, l'albero hash è parte integrante di dm-verity. Lo strumento cryptsetup genererà un albero hash per te. In alternativa, uno compatibile è definito qui:

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

Per formare l'hash, l'immagine di sistema viene suddivisa al livello 0 in blocchi da 4k, a ciascuno assegnato un hash SHA256. Il livello 1 è formato unendo solo quegli hash SHA256 in blocchi 4k, risultando in un'immagine molto più piccola. Lo strato 2 è formato in modo identico, con gli hash SHA256 dello strato 1.

Questo viene fatto fino a quando gli hash SHA256 del livello precedente non possono essere inseriti in un unico blocco. Quando ottieni lo SHA256 di quel blocco, hai l'hash di root dell'albero.

La dimensione dell'albero hash (e il corrispondente utilizzo dello spazio su disco) varia in base alla dimensione della partizione verificata. In pratica, la dimensione degli alberi di hash tende ad essere piccola, spesso inferiore a 30 MB.

Se hai un blocco in un livello che non è completamente riempito naturalmente dagli hash del livello precedente, dovresti riempirlo con zeri per ottenere il 4k previsto. Ciò ti consente di sapere che l'albero hash non è stato rimosso e viene invece completato con dati vuoti.

Per generare l'albero degli hash, concatena gli hash del livello 2 su quelli del livello 1, gli hash del livello 3 su quelli del livello 2 e così via. Scrivi tutto questo su disco. Nota che questo non fa riferimento al livello 0 dell'hash di root.

Per ricapitolare, l'algoritmo generale per costruire l'albero hash è il seguente:

  1. Scegli un salt casuale (codifica esadecimale).
  2. Unsparse la tua immagine di sistema in blocchi 4k.
  3. Per ogni blocco, ottieni il suo hash SHA256 (salato).
  4. Concatena questi hash per formare un livello
  5. Riempi il livello con gli 0 fino a un limite di blocco di 4k.
  6. Concatena il livello al tuo albero di hash.
  7. Ripeti i passaggi da 2 a 6 utilizzando il livello precedente come fonte per il successivo finché non avrai un solo hash.

Il risultato di questo è un singolo hash, che è il tuo hash di root. Questo e il tuo sale vengono utilizzati durante la costruzione della tabella di mappatura dm-verity.

Creazione della tabella di mappatura dm-verity

Costruisci la tabella di mappatura dm-verity, che identifica il dispositivo a blocchi (o destinazione) per il kernel e la posizione dell'albero hash (che ha lo stesso valore). Questa mappatura viene utilizzata per la generazione e l'avvio di fstab . La tabella identifica anche la dimensione dei blocchi e hash_start, la posizione di inizio dell'albero hash (in particolare, il suo numero di blocco dall'inizio dell'immagine).

Vedere cryptsetup per una descrizione dettagliata dei campi della tabella di mappatura del target di verity.

Firma della tabella dm-verity

Firma la tabella dm-verity per produrre una firma della tabella. Quando si verifica una partizione, la firma della tabella viene prima convalidata. Questo viene fatto contro una chiave sull'immagine di avvio in una posizione fissa. Le chiavi sono in genere incluse nei sistemi di build dei produttori per l'inclusione automatica sui dispositivi in ​​una posizione fissa.

Per verificare la partizione con questa firma e combinazione di chiavi:

  1. Aggiungi una chiave RSA-2048 in formato compatibile con libmincrypt alla partizione /boot in /verity_key . Identificare la posizione della chiave utilizzata per verificare l'albero hash.
  2. In fstab per la voce pertinente, aggiungi la verify ai flag fs_mgr .

Raggruppare la firma della tabella nei metadati

Raggruppa la firma della tabella e la tabella dm-verity nei metadati di verità. L'intero blocco di metadati ha una versione in modo che possa essere esteso, ad esempio per aggiungere un secondo tipo di firma o modificare alcuni ordini.

Come controllo di integrità, a ogni set di metadati della tabella viene associato un numero magico che aiuta a identificare la tabella. Poiché la lunghezza è inclusa nell'intestazione dell'immagine di sistema ext4, ciò fornisce un modo per cercare i metadati senza conoscere il contenuto dei dati stessi.

Questo assicura che tu non abbia scelto di verificare una partizione non verificata. In tal caso, l'assenza di questo numero magico interromperà il processo di verifica. Questo numero assomiglia a:
0xb001b001

I valori dei byte in esadecimale sono:

  • primo byte = b0
  • secondo byte = 01
  • terzo byte = b0
  • quarto byte = 01

Il diagramma seguente illustra la suddivisione dei metadati di verità:

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

E questa tabella descrive quei campi di metadati.

Tabella 1. Campi dei metadati di Verity

Campo Scopo Dimensione Valore
numero magico utilizzato da fs_mgr come controllo di integrità 4 byte 0xb001b001
versione utilizzato per eseguire la versione del blocco di metadati 4 byte attualmente 0
firma la firma della tabella in forma imbottita PKCS1.5 256 byte
lunghezza del tavolo la lunghezza della tabella dm-verity in byte 4 byte
tavolo la tabella dm-verity descritta in precedenza byte di lunghezza della tabella
imbottitura questa struttura è 0-imbottita a 32k di lunghezza 0

Ottimizzazione di dm-verity

Per ottenere le migliori prestazioni da dm-verity, dovresti:

  • Nel kernel, attiva NEON SHA-2 per ARMv7 e le estensioni SHA-2 per ARMv8.
  • Sperimenta diverse impostazioni read-ahead e prefetch_cluster per trovare la configurazione migliore per il tuo dispositivo.