Android 4.4 e versioni successive supportano l'Avvio verificato tramite la funzionalità facoltativa del kernel device-mapper-verity (dm-verity), che fornisce un controllo dell'integrità trasparente dei dispositivi di blocco. dm-verity contribuisce a impedire la presenza di rootkit persistenti che possono mantenere i privilegi di root e compromettere i dispositivi. Questa funzionalità consente agli utenti Android di assicurarsi che, all'avvio, il dispositivo sia nello stesso stato in cui si trovava al termine dell'ultima sessione di utilizzo.
Le app potenzialmente dannose (PHA) con privilegi di root possono nascondersi ai programmi di rilevamento e mascherarsi in altro modo. Il software di rooting può farlo perché spesso ha più privilegi dei rilevatori, il che consente al software di "mentire" ai programmi di rilevamento.
La funzionalità dm-verity ti consente di esaminare un dispositivo di blocco, il livello di archiviazione sottostante del file system, e di determinare se corrisponde alla configurazione prevista. Per farlo, utilizza un albero di hash crittografico. Per ogni blocco (in genere 4K), è presente un hash SHA256.
Poiché i valori hash sono archiviati in un albero di pagine, per verificare il resto dell'albero deve essere considerato attendibile solo l'hash "root" di primo livello. La possibilità di modificare uno dei blocchi equivarrebbe a violare l'hash crittografico. Consulta il seguente diagramma per una rappresentazione di questa struttura.

Figura 1.Tabella hash di dm-verity
Nella partizione di avvio è inclusa una chiave pubblica che deve essere verificata esternamente dal produttore del dispositivo. Questa chiave viene utilizzata per verificare la firma per l'hash e confermare che la partizione di sistema del dispositivo sia protetta e invariata.
Funzionamento
La protezione dm-verity si trova nel kernel. Pertanto, se il software di rooting compromette il sistema prima dell'avvio del kernel, mantiene questo accesso. Per ridurre questo rischio, la maggior parte dei produttori verifica il kernel utilizzando una chiave incorporata nel dispositivo. Questa chiave non è modificabile una volta che il dispositivo esce dalla fabbrica.
I produttori utilizzano questa chiave per verificare la firma sul bootloader di primo livello, che a sua volta verifica la firma nei livelli successivi, sul bootloader dell'app e infine sul kernel. Ogni produttore che vuole usufruire dell'avvio verificato deve avere un metodo per verificare l'integrità del kernel. Supponendo che il kernel sia stato verificato, può esaminare un dispositivo di blocco e verificarlo durante il montaggio.
Un modo per verificare un dispositivo di blocco è sottoporre direttamente ad hashing i relativi contenuti e confrontarli con un valore memorizzato. Tuttavia, il tentativo di verificare un intero dispositivo di blocco può richiedere un periodo di tempo prolungato e consumare molta energia del dispositivo. I dispositivi impiegavano molto tempo per l'avvio e si scaricavano notevolmente prima dell'uso.
Invece, dm-verity verifica i blocchi singolarmente e solo quando viene eseguito l'accesso a ciascuno. Quando viene letto in memoria, il blocco viene sottoposto ad hashing in parallelo. L'hash viene poi verificato nell'albero. Poiché la lettura del blocco è un'operazione così costosa, la latenza introdotta da questa verifica a livello di blocco è relativamente minima.
Se la verifica non va a buon fine, il dispositivo genera un errore di I/O che indica che il blocco non può essere letto. Sembra che il file system sia stato danneggiato, come previsto.
Le app possono scegliere di procedere senza i dati risultanti, ad esempio quando questi risultati non sono richiesti per la funzionalità principale dell'app. Tuttavia, se l'app non può continuare senza i dati, l'operazione non va a buon fine.
Correzione degli errori in avanti
Android 7.0 e versioni successive migliorano la robustezza di dm-verity con la correzione dell'errore di evoluzione (FEC). L'implementazione AOSP inizia con il codice di correzione degli errori Reed-Solomon comune e applica una tecnica chiamata interlacciamento per ridurre l'overhead dello spazio e aumentare il numero di blocchi danneggiati che possono essere recuperati. Per ulteriori dettagli sulla correzione degli errori, consulta Avvio verificato con applicazione rigorosa e correzione degli errori.Implementazione
Riepilogo
- Genera un'immagine di sistema ext4.
- Genera una struttura ad albero di hash per l'immagine.
- Crea una tabella dm-verity per l'albero di hash.
- Firma la tabella dm-verity per produrre una firma della tabella.
- Raggruppa la firma della tabella e la tabella dm-verity nei metadati di Verity.
- Concatena l'immagine di sistema, i metadati di Verity e l'albero di hash.
Per una descrizione dettagliata dell'albero di hash e della tabella dm-verity, consulta la pagina The Chromium Projects - Verified Boot.
Genera l'albero di hash
Come descritto nell'introduzione, l'albero di hash è parte integrante di dm-verity. Lo strumento cryptsetup genera un albero di hash per te. In alternativa, ne viene definito uno compatibile 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 a livello 0 in blocchi 4K, a ciascuno viene assegnato un hash SHA256. Il livello 1 viene formato unendo solo gli hash SHA256 in blocchi 4K, il che si traduce in un'immagine molto più piccola. Il livello 2 viene formato in modo identico, con gli hash SHA256 del livello 1.
L'operazione viene eseguita fino a quando gli hash SHA256 del livello precedente non possono essere inseriti in un singolo blocco. Quando ottieni l'SHA256 di quel blocco, hai l'hash principale dell'albero.
Le dimensioni dell'albero di hash (e l'utilizzo dello spazio su disco corrispondente) variano in base alle dimensioni della partizione verificata. In pratica, le dimensioni degli alberi di hash tendono a essere piccole, spesso inferiori a 30 MB.
Se in un livello è presente un blocco che non è completamente riempito in modo naturale dagli hash del livello precedente, devi riempirlo con zeri per ottenere i 4K previsti. In questo modo puoi sapere che l'albero hash non è stato rimosso ed è invece completato con dati vuoti.
Per generare l'albero di hash, concatena gli hash di livello 2 a quelli di livello 1, gli hash di livello 3 a quelli di livello 2 e così via. Scrivi tutto su disco. Tieni presente che non fa riferimento al livello 0 dell'hash radice.
Per riepilogare, l'algoritmo generale per la costruzione dell'albero di hash è il seguente:
- Scegli un valore salt casuale (codifica esadecimale).
- Decomprimere l'immagine di sistema in blocchi 4K.
- Per ogni blocco, ottieni il relativo hash SHA256 (con il sale).
- Concatena questi hash per formare un livello
- Inserisci zeri nel livello fino a un confine di blocchi di 4K.
- Collega il livello all'albero di hash.
- Ripeti i passaggi 2-6 utilizzando il livello precedente come origine per il successivo finché non hai un solo hash.
Il risultato è un singolo hash, ovvero l'hash principale. Questo valore e il tuo valore di salt vengono utilizzati durante la creazione della tabella di mappatura di dm-verity.
Crea la tabella di mappatura di dm-verity
Crea la tabella di mappatura dm-verity, che identifica il dispositivo di blocco (o target) per il kernel e la posizione della struttura ad albero di hash (che è lo stesso valore). Questa mappatura viene utilizzata per la generazione e l'avvio di fstab
. La tabella identifica anche le dimensioni dei blocchi e hash_start, la posizione iniziale dell'albero hash (in particolare, il numero di blocco dall'inizio dell'immagine).
Per una descrizione dettagliata dei campi della tabella di mappatura dei target di Verity, consulta cryptsetup.
Firma la tabella dm-verity
Firma la tabella dm-verity per produrre una firma della tabella. Quando viene verificata una tabella, viene convalidata prima la firma della tabella. Questo viene eseguito in base a una chiave sull'immagine di avvio in una posizione fissa. Le chiavi sono in genere incluse nei sistemi di compilazione dei produttori per l'inclusione automatica sui dispositivi in una posizione fissa.
Per verificare la partizione con questa firma e combinazione di chiavi:
- Aggiungi una chiave RSA-2048 in formato compatibile con libmincrypt alla
partizione
/boot
in/verity_key
. Identifica la posizione della chiave utilizzata per verificare l'albero di hash. - In fstab per la voce pertinente, aggiungi
verify
ai flagfs_mgr
.
Raggruppa la firma della tabella nei metadati
Raggruppa la firma della tabella e la tabella dm-verity nei metadati di Verity. L'intero blocco di metadati è sottoposto a controllo della versione, pertanto può essere esteso, ad esempio per aggiungere un secondo tipo di firma o modificare l'ordine.
Come controllo di congruenza, a ogni insieme di metadati della tabella è associato un numero magico che consente di identificare la tabella. Poiché la lunghezza è inclusa nell'intestazione dell'immagine di sistema ext4, questo fornisce un modo per cercare i metadati senza conoscere i contenuti dei dati stessi.
In questo modo, ti assicurerai di non aver scelto di verificare una partizione non verificata. In questo caso,
l'assenza di questo numero magico interrompe la procedura di verifica. Questo numero è simile a
0xb001b001
.
I valori in esadecimale dei byte sono:
- primo byte = b0
- secondo byte = 01
- terzo byte = b0
- quarto byte = 01
Il seguente diagramma mostra la suddivisione dei metadati di Verity:
<magic number>|<version>|<signature>|<table length>|<table>|<padding> \-------------------------------------------------------------------/ \----------------------------------------------------------/ | | | | 32K block content
Questa tabella descrive i campi dei metadati.
Tabella 1. Verifica i campi dei metadati
Campo | Finalità | Dimensioni | Valore |
---|---|---|---|
numero magico | utilizzato da fs_mgr come controllo di attendibilità | 4 byte | 0xb001b001 |
versione | utilizzato per eseguire la versione del blocco dei metadati | 4 byte | attualmente 0 |
firma | la firma della tabella in formato PKCS1.5 con spaziatura | 256 byte | |
Lunghezza della tabella | la lunghezza della tabella dm-verity in byte | 4 byte | |
tavolo | la tabella dm-verity descritta in precedenza | byte di lunghezza tabella | |
padding | questa struttura è riempita con zeri fino a 32.000 byte di lunghezza | 0 |
Ottimizza dm-verity
Per ottenere il massimo delle prestazioni da dm-verity, devi:
- Nel kernel, attiva NEON SHA-2 per ARMv7 e le estensioni SHA-2 per ARMv8.
- Prova diverse impostazioni di lettura anticipata e prefetch_cluster per trovare la configurazione migliore per il tuo dispositivo.