dm-verity implementieren

Android 4.4 und höher unterstützt den verifizierten Bootmodus über die optionale Kernelfunktion „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-Berechtigungen behalten und Geräte manipulieren können. Diese Funktion hilft Android-Nutzern, beim Starten eines Geräts sicherzugehen, dass es sich im selben Zustand befindet wie bei der letzten Verwendung.

Potenziell schädliche Anwendungen (Potentially Harmful Applications, PHAs) mit Root-Berechtigungen können sich vor Erkennungsprogrammen verstecken und sich anderweitig tarnen. Die Rooting-Software kann dies, weil sie oft mehr Berechtigungen hat als die Erkennungsprogramme, sodass sie den Erkennungsprogrammen „lügen“ kann.

Mit der dm-verity-Funktion können Sie ein Blockgerät, die zugrunde liegende Speicherschicht des Dateisystems, prüfen und feststellen, ob es der erwarteten Konfiguration entspricht. Dazu wird ein kryptografischer Hashbaum verwendet. Für jeden Block (in der Regel 4 KB) gibt es einen SHA256-Hash.

Da die Hash-Werte in einem Seitenbaum gespeichert sind, muss nur der Hashwert der obersten Ebene („Stamm“) für die Überprüfung des restlichen Baums vertrauenswürdig sein. Die Möglichkeit, einen der Blöcke zu ändern, würde dem Brechen des kryptografischen Hashes entsprechen. Das folgende Diagramm zeigt diese Struktur.

dm-verity-hash-table

Abbildung 1: dm-verity-Hashtabelle

Auf der Bootpartition ist ein öffentlicher Schlüssel enthalten, der vom Gerätehersteller extern verifiziert werden muss. Mit diesem Schlüssel wird die Signatur für diesen Hashwert überprüft und bestätigt, 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 manipuliert, bevor der Kernel gestartet wird, behält sie diesen Zugriff. Um dieses Risiko zu minimieren, verifizieren die meisten Hersteller den Kernel mit einem Schlüssel, der in das Gerät eingebrannt ist. Dieser Schlüssel kann nicht geändert werden, nachdem das Gerät das Werk verlassen hat.

Mit diesem Schlüssel wird die Signatur des Bootloaders der ersten Ebene überprüft, der wiederum die Signatur der nachfolgenden Ebenen, des App-Bootloaders und schließlich des Kernels überprüft. Jeder Hersteller, der Verified Boot nutzen möchte, sollte eine Methode zur Überprüfung der Integrität des Kernels haben. Angenommen, der Kernel wurde überprüft, kann er ein Blockgerät prüfen und es bei der Bereitstellung bestätigen.

Eine Möglichkeit zur Überprüfung eines Blockgeräts besteht darin, seinen Inhalt direkt zu hashen und mit einem gespeicherten Wert zu vergleichen. Der Versuch, ein gesamtes Blockgerät zu prüfen, kann jedoch sehr lange dauern und viel Strom verbrauchen. Das Starten der Geräte dauerte sehr lange und der Akku war vor der Verwendung bereits stark entladen.

Stattdessen werden Blöcke von dm-verity einzeln und nur dann geprüft, wenn auf sie zugegriffen wird. Beim Lesen in den Arbeitsspeicher wird der Block parallel gehasht. Der Hash wird dann im Baum überprüft. Da das Lesen des Blocks so aufwendig ist, ist die Latenz, die durch diese Blockebenenprüfung entsteht, vergleichsweise gering.

Wenn die Überprüfung fehlschlägt, generiert das Gerät einen I/O-Fehler, der angibt, dass der Block nicht gelesen werden kann. Offenbar ist das Dateisystem beschädigt, wie erwartet.

Apps können auch ohne die resultierenden Daten fortfahren, z. B. wenn diese Ergebnisse für die Hauptfunktion der App nicht erforderlich sind. Wenn die App jedoch ohne die Daten nicht fortgesetzt werden kann, schlägt sie fehl.

Vorwärtsfehlerkorrektur

Ab Android 7.0 wird die Robustheit von dm-verity durch die Vorwärtsfehlerkorrektur (FEC) verbessert. Die AOSP-Implementierung beginnt mit dem gängigen Reed-Solomon-Fehlerkorrekturcode und verwendet ein Verfahren namens Interleaving, um den Speicherplatzbedarf zu reduzieren und die Anzahl der beschädigten Blöcke zu erhöhen, die wiederhergestellt werden können. Weitere Informationen zu FEC finden Sie unter Streng erzwungener Verified Boot mit Fehlerkorrektur.

Implementierung

Zusammenfassung

  1. Erstellen Sie ein ext4-System-Image.
  2. Erstellen Sie einen Hash-Baum für dieses Bild.
  3. Erstellen Sie eine dm-verity-Tabelle für dieses Hash-Baum.
  4. Unterschreiben 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. Konkatenieren Sie das System-Image, die Verity-Metadaten und den Hash-Baum.

Eine detaillierte Beschreibung des Hashbaums und der dm-verity-Tabelle finden Sie unter The Chromium Projects – Verified Boot (in englischer Sprache).

Hash-Baum generieren

Wie in der Einführung beschrieben, ist der Hash-Baum ein wesentlicher Bestandteil von dm-verity. Das Tool cryptsetup generiert einen Hash-Baum für Sie. Alternativ ist hier eine kompatible Definition zu finden:

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

Zum Erstellen des Hashwerts wird das System-Image auf Ebene 0 in 4‑KB-Blöcke unterteilt, denen jeweils ein SHA256-Hash zugewiesen wird. Ebene 1 wird gebildet, indem nur diese SHA256-Hashes zu 4K-Blöcken zusammengeführt werden, was zu einem viel kleineren Bild führt. Schicht 2 wird auf identische Weise mit den SHA256-Hashes von Schicht 1 gebildet.

Dies geschieht so lange, bis die SHA256-Hashes der vorherigen Schicht in einen einzelnen Block passen. Wenn Sie den SHA256 dieses Blocks erhalten, haben Sie den Stamm-Hash des Baums.

Die Größe des Hashbaums (und die entsprechende Laufwerksspeichernutzung) variiert mit der Größe der geprüften Partition. In der Praxis sind Hashbäume in der Regel klein, oft weniger als 30 MB.

Wenn ein Block in einer Ebene nicht vollständig durch die Hashes der vorherigen Ebene gefüllt ist, sollten Sie ihn mit Nullen auffüllen, um die erwarteten 4 KB zu erreichen. So können Sie feststellen, dass der Hash-Baum nicht entfernt wurde, sondern stattdessen mit leeren Daten ausgefüllt ist.

Um den Hash-Baum zu generieren, verketten Sie die Hash-Werte der Schicht 2 mit denen der Schicht 1, die der Schicht 3 mit denen der Schicht 2 usw. Schreiben Sie alle diese Informationen auf die Festplatte. Hinweis: Dies bezieht sich nicht auf Schicht 0 des Stamm-Hashwerts.

Zur Wiederholung: Der allgemeine Algorithmus zum Erstellen des Hashbaums sieht so aus:

  1. Wählen Sie ein zufälliges Salt (hexadezimale Codierung) aus.
  2. Entfernen Sie die Sparse-Dateistruktur aus dem System-Image und teilen Sie es in 4‑KB-Blöcke auf.
  3. Rufen Sie für jeden Block den (gesalzenen) SHA256-Hash ab.
  4. Diese Hashes zu einem Level zusammenführen
  5. Fügen Sie dem Level Nullen hinzu, bis eine 4K-Blockgrenze erreicht ist.
  6. Fügen Sie die Ebene Ihrem Hash-Baum hinzu.
  7. Wiederholen Sie die Schritte 2 bis 6, wobei Sie die vorherige Ebene als Quelle für die nächste verwenden, bis Sie nur noch einen einzelnen Hash haben.

Das Ergebnis ist ein einzelner Hash, der als Root-Hash bezeichnet wird. Dieser und Ihr Salt werden beim Erstellen der dm-verity-Zuordnungstabelle verwendet.

DM-Verity-Zuordnungstabelle erstellen

Erstellen Sie die dm-verity-Zuordnungstabelle, die das Blockgerät (oder Ziel) für den Kernel und den Speicherort des Hashbaums angibt (dies ist derselbe Wert). Diese Zuordnung wird für die fstab-Generierung und das Booten verwendet. Die Tabelle enthält außerdem die Größe der Blöcke und den Hash-Start, den Startpunkt des Hash-Baums (genauer gesagt die Blocknummer vom Anfang des Bildes).

Eine ausführliche Beschreibung der Felder der Tabelle „Verity-Zielzuordnung“ finden Sie unter cryptsetup.

dm-verity-Tabelle signieren

Signieren Sie die dm-verity-Tabelle, um eine Tabellensignatur zu erstellen. Bei der Überprüfung einer Partition wird zuerst die Tabellensignatur validiert. Dies geschieht anhand eines Schlüssels in Ihrem Boot-Image an einer festen Stelle. Schlüssel sind in der Regel in den Build-Systemen der Hersteller enthalten, um Geräte an einem festen Standort automatisch einzubinden.

So bestätigen Sie die Partition mit dieser Signatur und Schlüsselkombination:

  1. Fügen Sie der Partition /boot unter /verity_key einen RSA-2048-Schlüssel im libmincrypt-kompatiblen Format hinzu. Geben Sie den Speicherort des Schlüssels an, der zum Verifizieren des Hashbaums verwendet wird.
  2. Fügen Sie in der fstab für den entsprechenden Eintrag den Flags fs_mgr die Zeichenfolge verify hinzu.

Tabellensignatur in Metadaten einbinden

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

Zur Fehlerprüfung ist jeder Tabelle eine magische Zahl zugewiesen, die zur Identifizierung der Tabelle dient. Da die Länge im Header des ext4-System-Images enthalten ist, können Sie nach den Metadaten suchen, ohne den Inhalt der Daten zu kennen.

So wird verhindert, dass Sie eine nicht bestätigte Partition bestätigen. Andernfalls wird der Bestätigungsvorgang aufgrund des Fehlens dieser magischen Zahl angehalten. Diese Zahl ähnelt 0xb001b001.

Die Bytewerte im Hexadezimalformat 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

In dieser Tabelle werden diese Metadatenfelder beschrieben.

Tabelle 1 Metadatenfelder prüfen

Feld Zweck Größe Wert
Magische Zahl Wird von fs_mgr als Plausibilitätscheck verwendet 4 Byte 0xb001b001
Version Wird verwendet, um den Metadatenblock zu versionieren. 4 Byte aktuell 0
Signatur die Signatur der Tabelle im PKCS1.5-ausgeglichenen Format 256 Byte
Tabellenlänge die Länge der dm-verity-Tabelle in Byte 4 Byte
Tisch die oben beschriebene dm-verity-Tabelle Bytes für Tabellenlänge
padding Diese Struktur wird auf 32.000 Byte aufgefüllt. 0

dm-verity optimieren

So erzielen Sie mit dm-verity die beste Leistung:

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