Wdrażanie dm-verity

Android 4.4 i nowsze wersje obsługują funkcję Verified Boot poprzez opcjonalną funkcję jądra Device-Mapper-Verity (dm-verity), która zapewnia przejrzyste sprawdzanie integralności urządzeń blokowych. dm-verity pomaga zapobiegać trwałym rootkitom, które mogą przechowywać uprawnienia roota i zagrażać urządzeniom. Ta funkcja pomaga użytkownikom Androida mieć pewność, że podczas uruchamiania urządzenia znajduje się ono w tym samym stanie, w jakim było ostatnio używane.

Potencjalnie szkodliwe aplikacje (PHA) z uprawnieniami roota mogą ukrywać się przed programami wykrywającymi i w inny sposób maskować się. Oprogramowanie do rootowania może to zrobić, ponieważ często jest bardziej uprzywilejowane niż detektory, umożliwiając oprogramowaniu „okłamywanie” programów wykrywających.

Funkcja dm-verity pozwala sprawdzić urządzenie blokowe, czyli podstawową warstwę pamięci masowej systemu plików, i określić, czy odpowiada ono oczekiwanej konfiguracji. Robi to za pomocą kryptograficznego drzewa skrótów. Dla każdego bloku (zwykle 4k) istnieje skrót SHA256.

Ponieważ wartości skrótu są przechowywane w drzewie stron, w celu sprawdzenia reszty drzewa można zaufać tylko skrótowi „głównemu” najwyższego poziomu. Możliwość modyfikacji któregokolwiek z bloków byłaby równoznaczna z złamaniem skrótu kryptograficznego. Poniższy diagram przedstawia tę strukturę.

dm-verity-hash-table

Rysunek 1. Tabela skrótów dm-verity

Na partycji rozruchowej znajduje się klucz publiczny, który musi zostać zweryfikowany zewnętrznie przez producenta urządzenia. Klucz ten służy do weryfikacji podpisu tego skrótu i ​​potwierdzenia, że ​​partycja systemowa urządzenia jest chroniona i niezmieniona.

Operacja

Ochrona dm-verity znajduje się w jądrze. Jeśli więc oprogramowanie do rootowania naruszy system, zanim pojawi się jądro, zachowa ten dostęp. Aby zminimalizować to ryzyko, większość producentów weryfikuje jądro za pomocą klucza wypalonego w urządzeniu. Klucz ten nie podlega zmianie po opuszczeniu fabryki przez urządzenie.

Producenci używają tego klucza do weryfikacji podpisu w programie ładującym pierwszego poziomu, który z kolei weryfikuje podpis na kolejnych poziomach, programie ładującym aplikacji i ostatecznie jądrze. Każdy producent chcący skorzystać z zweryfikowanego rozruchu powinien posiadać metodę sprawdzania integralności jądra. Zakładając, że jądro zostało zweryfikowane, może ono przyjrzeć się urządzeniu blokowemu i zweryfikować je po zamontowaniu.

Jednym ze sposobów weryfikacji urządzenia blokowego jest bezpośrednie mieszanie jego zawartości i porównanie jej z przechowywaną wartością. Jednak próba sprawdzenia całego urządzenia blokowego może zająć dłuższy czas i pochłonąć znaczną część mocy urządzenia. Uruchamianie urządzeń trwałoby długo, a następnie uległyby znacznemu rozładowaniu przed użyciem.

Zamiast tego dm-verity weryfikuje bloki indywidualnie i tylko wtedy, gdy każdy z nich jest dostępny. Po wczytaniu do pamięci blok jest mieszany równolegle. Hash jest następnie weryfikowany w górę drzewa. A ponieważ odczytanie bloku jest tak kosztowną operacją, opóźnienie wprowadzone przez weryfikację na poziomie bloku jest stosunkowo nominalne.

Jeśli weryfikacja nie powiedzie się, urządzenie generuje błąd we/wy wskazujący, że bloku nie można odczytać. Zgodnie z oczekiwaniami będzie wyglądać tak, jakby system plików został uszkodzony.

Aplikacje mogą zdecydować się na kontynuację bez uzyskanych danych, na przykład wtedy, gdy wyniki te nie są wymagane do podstawowej funkcji aplikacji. Jeśli jednak aplikacja nie będzie mogła kontynuować działania bez danych, zakończy się niepowodzeniem.

Korekta błędów w przód

Android 7.0 i nowsze wersje poprawiają niezawodność dm-verity dzięki korekcji błędów w przód (FEC). Implementacja AOSP rozpoczyna się od wspólnego kodu korekcji błędów Reeda-Solomona i stosuje technikę zwaną przeplataniem, aby zmniejszyć obciążenie przestrzeni i zwiększyć liczbę uszkodzonych bloków, które można odzyskać. Aby uzyskać więcej informacji na temat FEC, zobacz Ściśle wymuszony zweryfikowany rozruch z korekcją błędów .

Realizacja

Streszczenie

  1. Wygeneruj obraz systemu ext4.
  2. Wygeneruj drzewo skrótów dla tego obrazu.
  3. Zbuduj tabelę dm-verity dla tego drzewa mieszającego.
  4. Podpisz tę tabelę dm-verity, aby utworzyć podpis tabeli.
  5. Połącz sygnaturę tabeli i tabelę dm-verity w metadane rzeczywistości.
  6. Połącz obraz systemu, metadane dotyczące rzeczywistości i drzewo skrótów.

Zobacz Projekty Chromium - Verified Boot , aby uzyskać szczegółowy opis drzewa skrótów i tabeli dm-verity.

Generowanie drzewa skrótów

Jak opisano we wstępie, drzewo mieszające jest integralną częścią dm-verity. Narzędzie cryptsetup wygeneruje dla Ciebie drzewo skrótów. Alternatywnie, tutaj zdefiniowano kompatybilny:

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

Aby utworzyć skrót, obraz systemu jest dzielony w warstwie 0 na 4k bloków, z których każdy ma przypisany skrót SHA256. Warstwa 1 jest tworzona przez połączenie tylko skrótów SHA256 w bloki o wielkości 4 tys., co daje znacznie mniejszy obraz. Warstwa 2 jest utworzona identycznie, z skrótami SHA256 Warstwy 1.

Odbywa się to do momentu, gdy skróty SHA256 poprzedniej warstwy zmieszczą się w jednym bloku. Kiedy otrzymasz SHA256 tego bloku, masz skrót główny drzewa.

Rozmiar drzewa skrótów (i odpowiadające mu wykorzystanie miejsca na dysku) różni się w zależności od rozmiaru zweryfikowanej partycji. W praktyce rozmiar drzew haszujących jest zwykle niewielki, często mniejszy niż 30 MB.

Jeśli masz blok w warstwie, która nie jest całkowicie wypełniona w sposób naturalny skrótami z poprzedniej warstwy, powinieneś wypełnić go zerami, aby osiągnąć oczekiwane 4k. Dzięki temu wiesz, że drzewo skrótów nie zostało usunięte i zamiast tego zostało uzupełnione pustymi danymi.

Aby wygenerować drzewo skrótów, połącz skróty warstwy 2 z hashami warstwy 1, skróty warstwy 3 z hashami warstwy 2 i tak dalej. Zapisz to wszystko na dysku. Zauważ, że nie odnosi się to do warstwy 0 skrótu głównego.

Podsumowując, ogólny algorytm konstruowania drzewa skrótów jest następujący:

  1. Wybierz losową sól (kodowanie szesnastkowe).
  2. Rozparuj obraz systemu na bloki o wielkości 4 tys.
  3. Dla każdego bloku uzyskaj jego (solony) skrót SHA256.
  4. Połącz te skróty, aby utworzyć poziom
  5. Dokończ poziom zerami do granicy bloku 4k.
  6. Połącz poziom z drzewem skrótów.
  7. Powtarzaj kroki 2-6, używając poprzedniego poziomu jako źródła następnego, aż będziesz mieć tylko jeden skrót.

Wynikiem tego jest pojedynczy skrót, który jest skrótem głównym. To i twoja sól są używane podczas konstruowania tabeli mapowania dm-verity.

Budowanie tabeli mapowania dm-verity

Zbuduj tabelę mapowania dm-verity, która identyfikuje urządzenie blokowe (lub cel) dla jądra i lokalizację drzewa skrótów (które ma tę samą wartość). To mapowanie jest używane do generowania i uruchamiania fstab . Tabela określa również rozmiar bloków i hash_start, lokalizację początkową drzewa mieszającego (w szczególności numer jego bloku od początku obrazu).

Zobacz cryptsetup , aby uzyskać szczegółowy opis pól tabeli mapowania celu rzeczywistego.

Podpisywanie tabeli dm-verity

Podpisz tabelę dm-verity, aby utworzyć podpis tabeli. Podczas weryfikacji partycji najpierw sprawdzany jest podpis tabeli. Odbywa się to za pomocą klucza na obrazie rozruchowym w ustalonej lokalizacji. Klucze są zwykle dołączane do systemów kompilacji producentów w celu automatycznego dołączania do urządzeń w stałej lokalizacji.

Aby zweryfikować partycję za pomocą tej kombinacji podpisu i klawisza:

  1. Dodaj klucz RSA-2048 w formacie zgodnym z libmincrypt do partycji /boot w /verity_key . Zidentyfikuj lokalizację klucza użytego do weryfikacji drzewa skrótów.
  2. W fstab odpowiedniego wpisu dodaj opcję verify do flag fs_mgr .

Łączenie podpisu tabeli w metadane

Połącz sygnaturę tabeli i tabelę dm-verity w metadane rzeczywistości. Cały blok metadanych jest wersjonowany, co pozwala na jego rozbudowę, np. o dodanie drugiego rodzaju podpisu lub zmianę kolejności.

Aby sprawdzić poprawność, z każdym zestawem metadanych tabeli jest powiązana magiczna liczba, która pomaga zidentyfikować tabelę. Ponieważ długość jest zawarta w nagłówku obrazu systemu ext4, umożliwia to wyszukiwanie metadanych bez znajomości zawartości samych danych.

Dzięki temu upewnisz się, że nie wybrałeś weryfikacji niezweryfikowanej partycji. Jeśli tak, brak tej magicznej liczby zatrzyma proces weryfikacji. Liczba ta przypomina:
0xb001b001

Wartości bajtów w formacie szesnastkowym to:

  • pierwszy bajt = b0
  • drugi bajt = 01
  • trzeci bajt = b0
  • czwarty bajt = 01

Poniższy diagram przedstawia rozkład metadanych dotyczących rzeczywistości:

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

Ta tabela opisuje te pola metadanych.

Tabela 1. Pola metadanych dotyczących rzeczywistości

Pole Zamiar Rozmiar Wartość
magiczny numer używany przez fs_mgr jako kontrola poprawności 4 bajty 0xb001b001
wersja używany do wersjonowania bloku metadanych 4 bajty obecnie 0
podpis podpis tabeli w formie wyściełanej PKCS1.5 256 bajtów
długość stołu długość tablicy dm-verity w bajtach 4 bajty
tabela tabela prawdziwości dm opisana wcześniej bajty długości tabeli
wyściółka ta struktura jest wypełniona zerem i ma długość 32 tys 0

Optymalizacja dm-verity

Aby uzyskać najlepszą wydajność dm-verity, powinieneś:

  • W jądrze włącz NEON SHA-2 dla ARMv7 i rozszerzenia SHA-2 dla ARMv8.
  • Eksperymentuj z różnymi ustawieniami odczytu z wyprzedzeniem i prefetch_cluster, aby znaleźć najlepszą konfigurację dla swojego urządzenia.