Implementierung von dm-verity

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

Android 4.4 und höher unterstützt Verified Boot über die optionale Kernel-Funktion Device-Mapper-Verity (dm-Verity), die eine transparente Integritätsprüfung von Blockgeräten ermöglicht. dm-verity hilft, persistente Rootkits zu verhindern, die Root-Rechte behalten und Geräte kompromittieren können. Diese Funktion hilft Android-Benutzern, beim Booten eines Geräts sicherzustellen, dass es sich im selben Zustand befindet wie bei der letzten Verwendung.

Potenziell schädliche Anwendungen (PHAs) mit Root-Rechten können sich vor Erkennungsprogrammen verstecken und sich anderweitig maskieren. Die Rooting-Software kann dies tun, da sie oft privilegierter ist als die Detektoren, was es der Software ermöglicht, die Erkennungsprogramme zu „lügen“.

Mit der Funktion dm-verity können Sie sich ein Blockgerät, die zugrunde liegende Speicherschicht des Dateisystems, ansehen und feststellen, ob es der erwarteten Konfiguration entspricht. Dies geschieht mithilfe eines kryptografischen Hash-Baums. Für jeden Block (normalerweise 4k) gibt es einen SHA256-Hash.

Da die Hash-Werte in einem Baum von Seiten gespeichert werden, muss nur dem „Root“-Hash der obersten Ebene vertraut werden, um den Rest des Baums zu überprüfen. Die Möglichkeit, einen der Blöcke zu ändern, wäre gleichbedeutend mit dem Brechen des kryptografischen Hashs. Siehe das folgende Diagramm für eine Darstellung dieser Struktur.

dm-verity-hash-table

Abbildung 1. Hash-Tabelle von dm-verity

Auf der Bootpartition ist ein öffentlicher Schlüssel enthalten, der extern vom Gerätehersteller verifiziert werden muss. Dieser Schlüssel wird verwendet, um die Signatur für diesen Hash zu überprüfen und zu bestätigen, dass die Systempartition des Geräts geschützt und unverändert ist.

Betrieb

Der dm-verity-Schutz befindet sich im Kernel. Wenn also Rooting-Software das System kompromittiert, bevor der Kernel auftaucht, behält sie diesen Zugriff. Um dieses Risiko zu mindern, verifizieren die meisten Hersteller den Kernel mit einem in das Gerät eingebrannten Schlüssel. Dieser Schlüssel kann nicht geändert werden, sobald das Gerät das Werk verlässt.

Hersteller verwenden diesen Schlüssel, um die Signatur auf dem Bootloader der ersten Ebene zu überprüfen, der wiederum die Signatur auf nachfolgenden Ebenen, dem Anwendungs-Bootloader und schließlich dem Kernel, überprüft. Jeder Hersteller, der den verifizierten Bootvorgang nutzen möchte, sollte über eine Methode verfügen, um die Integrität des Kernels zu überprüfen. Unter der Annahme, dass der Kernel verifiziert wurde, kann der Kernel ein Blockgerät betrachten und es verifizieren, während es gemountet wird.

Eine Möglichkeit, ein Blockgerät zu verifizieren, besteht darin, seinen Inhalt direkt zu hashen und mit einem gespeicherten Wert zu vergleichen. Der Versuch, ein gesamtes Blockgerät zu verifizieren, kann jedoch längere Zeit in Anspruch nehmen und einen Großteil der Energie eines Geräts verbrauchen. Das Hochfahren der Geräte würde lange dauern und dann vor der Verwendung erheblich entleert werden.

Stattdessen verifiziert dm-verity Blöcke einzeln und nur dann, wenn auf jeden zugegriffen wird. Beim Einlesen in den Speicher wird der Block parallel gehasht. Der Hash wird dann den Baum hinauf verifiziert. Und da das Lesen des Blocks eine so teure Operation ist, ist die Latenz, die durch diese Überprüfung auf Blockebene eingeführt wird, vergleichsweise gering.

Wenn die Überprüfung fehlschlägt, generiert das Gerät einen E/A-Fehler, der angibt, dass der Block nicht gelesen werden kann. Es sieht so aus, als wäre das Dateisystem erwartungsgemäß beschädigt.

Anwendungen können sich dafür entscheiden, ohne die resultierenden Daten fortzufahren, z. B. wenn diese Ergebnisse für die primäre Funktion der Anwendung nicht erforderlich sind. Wenn die Anwendung jedoch ohne die Daten nicht fortgesetzt werden kann, schlägt sie fehl.

Vorwärtsfehlerkorrektur

Android 7.0 und höher verbessert die Robustheit von dm-verity mit Vorwärtsfehlerkorrektur (FEC). Die AOSP-Implementierung beginnt mit dem üblichen Reed-Solomon -Fehlerkorrekturcode und wendet eine Technik namens Interleaving an, um den Speicherplatz-Overhead zu reduzieren und die Anzahl beschädigter Blöcke zu erhöhen, die wiederhergestellt werden können. Weitere Einzelheiten zu FEC finden Sie unter Strictly Enforced Verified Boot with Error Correction .

Implementierung

Zusammenfassung

  1. Generieren Sie ein ext4-Systemabbild.
  2. Generieren Sie einen Hash-Baum für dieses Bild.
  3. Erstellen Sie eine dm-Verity-Tabelle für diesen Hash-Baum.
  4. Signieren Sie diese dm-verity-Tabelle , um eine Tabellensignatur zu erstellen.
  5. Bündeln Sie die Tabellensignatur und die dm-verity-Tabelle in Verity-Metadaten.
  6. Verketten Sie das Systemabbild, die Verity-Metadaten und den Hashbaum.

SieheThe Chromium Projects – Verified Boot für eine detaillierte Beschreibung des Hash-Baums und der dm-verity-Tabelle.

Generieren des Hashbaums

Wie in der Einleitung beschrieben, ist der Hashbaum integraler Bestandteil von dm-verity. Das cryptsetup -Tool generiert einen Hash-Baum für Sie. Alternativ wird hier ein kompatibles definiert:

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

Um den Hash zu bilden, wird das Systemabbild auf Schicht 0 in 4k-Blöcke aufgeteilt, denen jeweils ein SHA256-Hash zugewiesen wird. Schicht 1 wird gebildet, indem nur diese SHA256-Hashes zu 4k-Blöcken zusammengefügt werden, was zu einem viel kleineren Bild führt. Layer 2 ist identisch aufgebaut, mit den SHA256-Hashes von Layer 1.

Dies wird so lange durchgeführt, bis die SHA256-Hashes der vorherigen Schicht in einen einzigen Block passen. Wenn Sie den SHA256 dieses Blocks erhalten, haben Sie den Root-Hash des Baums.

Die Größe des Hash-Baums (und die entsprechende Speicherplatznutzung) variiert mit der Größe der verifizierten Partition. In der Praxis ist die Größe von Hashbäumen eher klein, oft weniger als 30 MB.

Wenn Sie einen Block in einer Ebene haben, der nicht vollständig natürlich durch die Hashes der vorherigen Ebene gefüllt ist, sollten Sie ihn mit Nullen auffüllen, um die erwarteten 4k zu erreichen. Dadurch wissen Sie, dass der Hash-Baum nicht entfernt wurde und stattdessen mit leeren Daten vervollständigt wurde.

Um den Hash-Baum zu generieren, verketten Sie die Hashes der Schicht 2 mit denen für Schicht 1, die Hashes der Schicht 3 mit denen der Schicht 2 und so weiter. Schreiben Sie all dies auf die Festplatte. Beachten Sie, dass dies nicht auf Layer 0 des Root-Hashs verweist.

Zusammenfassend lässt sich sagen, dass der allgemeine Algorithmus zum Erstellen des Hash-Baums wie folgt lautet:

  1. Wählen Sie ein zufälliges Salt (hexadezimale Kodierung).
  2. Teilen Sie Ihr Systemabbild in 4k-Blöcke auf.
  3. Holen Sie sich für jeden Block seinen (gesalzenen) SHA256-Hash.
  4. Verketten Sie diese Hashes, um eine Ebene zu bilden
  5. Füllen Sie die Ebene mit 0s bis zu einer 4k-Blockgrenze auf.
  6. Verketten Sie die Ebene mit Ihrem Hash-Baum.
  7. Wiederholen Sie die Schritte 2-6, indem Sie die vorherige Ebene als Quelle für die nächste verwenden, bis Sie nur noch einen einzigen Hash haben.

Das Ergebnis davon ist ein einzelner Hash, der Ihr Root-Hash ist. Dies und Ihr Salt werden während der Erstellung Ihrer dm-verity-Mapping-Tabelle verwendet.

Erstellen der dm-verity-Zuordnungstabelle

Erstellen Sie die dm-verity-Zuordnungstabelle, die das Blockgerät (oder Ziel) für den Kernel und den Speicherort des Hash-Baums (der derselbe Wert ist) identifiziert. Diese Zuordnung wird zum Generieren und Booten von fstab verwendet. Die Tabelle identifiziert auch die Größe der Blöcke und hash_start, die Startposition des Hash-Baums (insbesondere seine Blocknummer vom Anfang des Bildes).

Siehe cryptsetup für eine detaillierte Beschreibung der Felder der Verity-Target-Mapping-Tabelle.

Signieren der dm-Verity-Tabelle

Signieren Sie die dm-verity-Tabelle, um eine Tabellensignatur zu erstellen. Beim Verifizieren einer Partition wird zuerst die Tabellensignatur validiert. Dies geschieht gegen einen Schlüssel auf Ihrem Boot-Image an einem festen Ort. Schlüssel sind in der Regel in den Build-Systemen der Hersteller enthalten, um sie automatisch auf Geräten an einem festen Ort einzufügen.

So überprüfen Sie die Partition mit dieser Signatur und Schlüsselkombination:

  1. Fügen Sie einen RSA-2048-Schlüssel im libmincrypt-kompatiblen Format zur /boot Partition unter /verity_key . Identifizieren Sie den Speicherort des Schlüssels, der zum Verifizieren des Hash-Baums verwendet wird.
  2. Fügen Sie in der fstab für den relevanten Eintrag den fs_mgr Flags verify hinzu.

Bündeln der Tabellensignatur in Metadaten

Bündeln Sie die Tabellensignatur und die dm-verity-Tabelle in Verity-Metadaten. Der gesamte Block von Metadaten ist versioniert, sodass er erweitert werden kann, z. B. um eine zweite Art von Signatur hinzuzufügen oder eine Reihenfolge zu ändern.

Als Plausibilitätsprüfung wird jedem Satz von Tabellenmetadaten eine magische Zahl zugeordnet, die hilft, die Tabelle zu identifizieren. Da die Länge im ext4-System-Image-Header enthalten ist, bietet dies eine Möglichkeit, nach den Metadaten zu suchen, ohne den Inhalt der Daten selbst zu kennen.

Dadurch wird sichergestellt, dass Sie nicht ausgewählt haben, eine nicht verifizierte Partition zu verifizieren. Wenn dies der Fall ist, wird das Fehlen dieser magischen Zahl den Überprüfungsprozess anhalten. Diese Nummer ähnelt:
0xb001b001

Die Bytewerte in Hex sind:

  • erstes Byte = b0
  • zweites Byte = 01
  • drittes Byte = b0
  • viertes Byte = 01

Das folgende Diagramm zeigt die Aufschlüsselung der Verity-Metadaten:

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

Und diese Tabelle beschreibt diese Metadatenfelder.

Tabelle 1. Verity-Metadatenfelder

Aufstellen Zweck Größe Wert
magische Zahl Wird von fs_mgr als Plausibilitätsprüfung verwendet 4 Bytes 0xb001b001
Ausführung Wird verwendet, um den Metadatenblock zu versionieren 4 Bytes aktuell 0
Unterschrift die Signatur der Tabelle in PKCS1.5-aufgefüllter Form 256 Byte
Tischlänge die Länge der dm-Verity-Tabelle in Bytes 4 Bytes
Tisch die zuvor beschriebene dm-verity-Tabelle Tabellenlänge Bytes
Polsterung Diese Struktur ist mit 0 aufgefüllt und hat eine Länge von 32 KB 0

Optimierung von dm-verity

Um die beste Leistung aus dm-verity herauszuholen, sollten Sie:

  • Aktivieren Sie im Kernel NEON SHA-2 für ARMv7 und die SHA-2-Erweiterungen für ARMv8.
  • Experimentieren Sie mit verschiedenen Read-Ahead- und Prefetch_cluster-Einstellungen, um die beste Konfiguration für Ihr Gerät zu finden.