Implementierung von dm-verity

Android 4.4 und höher unterstützt Verified Boot über die optionale Kernelfunktion „device-mapper-verity“ (dm-verity), die eine transparente Integritätsprüfung von Blockgeräten ermöglicht. dm-verity hilft dabei, hartnäckige Rootkits zu verhindern, die Root-Rechte behalten und Geräte kompromittieren können. Mit dieser Funktion können Android-Benutzer beim Booten eines Geräts sicherstellen, dass es sich in demselben Zustand befindet wie bei der letzten Verwendung.

Potenziell schädliche Anwendungen (PHAs) mit Root-Rechten können sich vor Erkennungsprogrammen verstecken und sich auf andere Weise tarnen. Die Rooting-Software kann dies tun, da sie häufig über größere Privilegien als die Detektoren verfügt und es der Software somit ermöglicht, die Erkennungsprogramme „anzulügen“.

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

Da die Hash-Werte in einem Seitenbaum 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. Eine Darstellung dieser Struktur finden Sie im folgenden Diagramm.

dm-verity-hash-tabelle

Abbildung 1. dm-verity-Hash-Tabelle

Auf der Boot-Partition ist ein öffentlicher Schlüssel enthalten, der vom Gerätehersteller extern 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 hochfährt, behält sie diesen Zugriff. Um dieses Risiko zu mindern, überprüfen die meisten Hersteller den Kernel mithilfe eines in das Gerät eingebrannten Schlüssels. 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 Start nutzen möchte, sollte über eine Methode zur Überprüfung der Integrität des Kernels verfügen. Vorausgesetzt, der Kernel wurde verifiziert, kann der Kernel ein Blockgerät betrachten und es beim Mounten überprüfen.

Eine Möglichkeit, ein Blockgerät zu verifizieren, besteht darin, seinen Inhalt direkt zu hashen und ihn mit einem gespeicherten Wert zu vergleichen. Der Versuch, ein gesamtes Blockgerät zu verifizieren, kann jedoch längere Zeit dauern 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 entladen werden.

Stattdessen überprüft dm-verity Blöcke einzeln und nur dann, wenn auf jeden einzelnen zugegriffen wird. Beim Einlesen in den Speicher wird der Block parallel gehasht. Der Hash wird dann im Baum überprüft. Und da das Lesen des Blocks ein so teurer Vorgang ist, ist die durch diese Überprüfung auf Blockebene verursachte Latenz vergleichsweise gering.

Wenn die Überprüfung fehlschlägt, generiert das Gerät einen E/A-Fehler, der darauf hinweist, 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, beispielsweise 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 durch Forward Error Correction (FEC). Die AOSP-Implementierung beginnt mit dem allgemeinen Reed-Solomon -Fehlerkorrekturcode und wendet eine Technik namens Interleaving an, um den Speicherplatzaufwand 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. Erzeugen 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 Systembild, die Verity-Metadaten und den Hash-Baum.

Eine ausführliche Beschreibung des Hash-Baums und der dm-verity-Tabelle finden Sie im Dokument „The Chromium Projects – Verified Boot“ .

Generieren des Hash-Baums

Wie in der Einleitung beschrieben, ist der Hash-Baum ein wesentlicher 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 Ebene 0 in 4.000 Blöcke aufgeteilt, denen jeweils ein SHA256-Hash zugewiesen wird. Schicht 1 wird gebildet, indem nur diese SHA256-Hashes zu 4-KB-Blöcken zusammengefügt werden, was zu einem viel kleineren Bild führt. Schicht 2 ist identisch aufgebaut, mit den SHA256-Hashes von Schicht 1.

Dies wird so lange durchgeführt, bis die SHA256-Hashes der vorherigen Schicht in einen einzelnen 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 überprüften Partition. In der Praxis ist die Größe von Hash-Bäumen tendenziell klein und beträgt oft weniger als 30 MB.

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

Um den Hash-Baum zu generieren, verketten Sie die Hashes der Schicht 2 mit denen der Schicht 1, die Hashes der Schicht 3 mit denen der Schicht 2 usw. Schreiben Sie das alles auf die Festplatte. Beachten Sie, dass dies nicht auf Ebene 0 des Root-Hashs verweist.

Um es noch einmal zusammenzufassen: Der allgemeine Algorithmus zum Erstellen des Hash-Baums lautet wie folgt:

  1. Wählen Sie ein zufälliges Salt (hexadezimale Kodierung).
  2. Teilen Sie Ihr System-Image in 4K-Blöcke auf.
  3. Erhalten Sie für jeden Block seinen (gesalzenen) SHA256-Hash.
  4. Verketten Sie diese Hashes, um eine Ebene zu bilden
  5. Füllen Sie das Level mit Nullen auf, bis eine 4k-Blockgrenze erreicht ist.
  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 ist ein einzelner Hash, der Ihr Root-Hash ist. Dies und Ihr Salt werden während der Erstellung Ihrer dm-verity-Zuordnungstabelle 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 denselben Wert hat) identifiziert. Diese Zuordnung wird für die fstab Generierung und das Booten verwendet. Die Tabelle identifiziert auch die Größe der Blöcke und hash_start, den Startort des Hash-Baums (insbesondere seine Blocknummer vom Anfang des Bildes).

Eine ausführliche Beschreibung der Felder der Verity-Zielzuordnungstabelle finden Sie unter cryptsetup .

Signieren der dm-verity-Tabelle

Signieren Sie die dm-verity-Tabelle, um eine Tabellensignatur zu erstellen. Bei der Überprüfung einer Partition wird zunächst die Tabellensignatur validiert. Dies erfolgt über einen Schlüssel in Ihrem Boot-Image an einem festen Ort. Schlüssel sind in der Regel in den Build-Systemen der Hersteller enthalten, um sie automatisch in Geräte an einem festen Standort einzubinden.

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

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

Bündelung der Tabellensignatur in Metadaten

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 eine Reihenfolge zu ändern.

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

Dadurch wird sichergestellt, dass Sie sich nicht für die Überprüfung einer nicht überprüften Partition entschieden haben. Wenn dies der Fall ist, führt das Fehlen dieser magischen Zahl dazu, dass der Verifizierungsprozess gestoppt wird. Diese Zahl ä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 Wahrheitsmetadaten:

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

Und diese Tabelle beschreibt diese Metadatenfelder.

Tabelle 1. Verity-Metadatenfelder

Feld Zweck Größe Wert
magische Zahl Wird von fs_mgr als Plausibilitätsprüfung verwendet 4 Bytes 0xb001b001
Ausführung Wird zur Versionierung des Metadatenblocks verwendet 4 Bytes derzeit 0
Unterschrift die Signatur der Tabelle in PKCS1.5-gefü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 auf eine Länge von 32 KB aufgefüllt 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.