Wdrażanie dm-verity

Android 4.4 i nowsze obsługują funkcję Verified Boot za pośrednictwem opcjonalnej funkcji 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ą utrzymywać uprawnienia roota i naruszać urządzenia. Ta funkcja pomaga użytkownikom Androida upewnić się, że podczas uruchamiania urządzenia jest ono w tym samym stanie, w jakim było ostatnio używane.

Potencjalnie szkodliwe aplikacje (PHA) z uprawnieniami administratora mogą ukrywać się przed programami wykrywającymi i w inny sposób maskować się. Oprogramowanie do rootowania może to zrobić, ponieważ jest często bardziej uprzywilejowane niż wykrywacze, co pozwala oprogramowaniu „kłamać” programom wykrywającym.

Funkcja dm-verity pozwala spojrzeć na urządzenie blokowe, podstawową warstwę pamięci systemu plików i określić, czy pasuje ona do oczekiwanej konfiguracji. Robi to za pomocą kryptograficznego drzewa mieszającego. Dla każdego bloku (zazwyczaj 4k) istnieje skrót SHA256.

Ponieważ wartości skrótu są przechowywane w drzewie stron, tylko skrót „główny” najwyższego poziomu musi być zaufany, aby zweryfikować resztę drzewa. Możliwość modyfikacji dowolnego z bloków byłaby równoznaczna z złamaniem skrótu kryptograficznego. Zobacz poniższy diagram, aby zobrazować tę strukturę.

dm-verity-hash-tabela

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

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

Operacja

Ochrona dm-verity znajduje się w jądrze. Więc jeśli oprogramowanie rootujące 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 na urządzeniu. Klucza tego nie można zmienić, gdy urządzenie opuści fabrykę.

Producenci używają tego klucza do weryfikacji podpisu na bootloaderze pierwszego poziomu, który z kolei weryfikuje podpis na kolejnych poziomach, bootloaderze aplikacji i ewentualnie jądrze. Każdy producent, który chce skorzystać ze zweryfikowanego rozruchu , powinien dysponować metodą weryfikacji integralności jądra. Zakładając, że jądro zostało zweryfikowane, jądro może spojrzeć na urządzenie blokowe i zweryfikować je podczas montażu.

Jednym ze sposobów weryfikacji urządzenia blokowego jest bezpośrednie zahaszowanie jego zawartości i porównanie ich z przechowywaną wartością. Jednak próba zweryfikowania całego urządzenia blokowego może zająć dłuższy czas i zużywać znaczną część energii urządzenia. Urządzenia wymagałyby długiego czasu uruchamiania, a następnie byłyby znacznie opróżniane przed użyciem.

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

Jeśli weryfikacja nie powiedzie się, urządzenie generuje błąd we/wy wskazujący, że blok nie może być odczytany. Wygląda to tak, jakby system plików został uszkodzony, zgodnie z oczekiwaniami.

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

Korekcja błędów w przód

Android 7.0 i nowszy poprawia niezawodność dm-verity dzięki korekcji błędów do przodu (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 Strictly Enforced Verified Boot with Error Correction .

Realizacja

Streszczenie

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

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

Generowanie drzewa haszującego

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

<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 bloki 4k, z których każdy ma przypisany skrót SHA256. Warstwa 1 jest tworzona przez połączenie tylko tych skrótów SHA256 w bloki 4k, co daje znacznie mniejszy obraz. Warstwa 2 jest utworzona identycznie, z hashami SHA256 warstwy 1.

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

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

Jeśli masz blok w warstwie, który nie jest całkowicie naturalnie wypełniony hashami poprzedniej warstwy, powinieneś uzupełnić go zerami, aby osiągnąć oczekiwane 4k. Dzięki temu wiesz, że drzewo haszujące 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 haszami warstwy 1, warstwy 3 z haszami 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 mieszającego wygląda następująco:

  1. Wybierz losową sól (kodowanie szesnastkowe).
  2. Rozłóż obraz systemu na bloki 4k.
  3. Dla każdego bloku uzyskaj jego (solony) skrót SHA256.
  4. Połącz te skróty, aby utworzyć poziom
  5. Uzupełnij poziom zerami do granicy bloku 4k.
  6. Połącz poziom z haszem.
  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 hash, który jest hashem głównym. To i twoja sól są używane podczas budowy twojej tabeli mapowania dm-verity.

Budowanie tabeli mapowania dm-verity

Zbuduj tablicę mapowania dm-verity, która identyfikuje urządzenie blokowe (lub cel) dla jądra i lokalizację drzewa mieszającego (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, początkową lokalizację drzewa haszującego (w szczególności jego numer bloku od początku obrazu).

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

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 w obrazie rozruchowym w stałej 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 sygnatury i kombinacji klawiszy:

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

Dołączanie podpisu tabeli do metadanych

Połącz sygnaturę tabeli i tabelę dm-verity w metadane verity. Cały blok metadanych jest wersjonowany, więc można go rozszerzyć, na przykład dodać drugi rodzaj sygnatury lub zmienić kolejność.

W ramach kontroli poprawności 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, zapewnia to sposób wyszukiwania metadanych bez znajomości zawartości samych danych.

Daje to pewność, że nie wybrałeś weryfikacji niezweryfikowanej partycji. Jeśli tak, brak tego magicznego numeru spowoduje zatrzymanie procesu weryfikacji. Ta liczba przypomina:
0xb001b001

Wartości bajtów w szesnastce to:

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

Poniższy diagram przedstawia podział metadanych prawdziwości:

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

A ta tabela opisuje te pola metadanych.

Tabela 1. Pola metadanych Verity

Pole Zamiar Rozmiar Wartość
magiczny numer używany przez fs_mgr jako kontrola zdrowia 4 bajty 0xb001b001
wersja używany do wersji bloku metadanych 4 bajty obecnie 0
podpis podpis tabeli w formie wypełnionej PKCS1.5 256 bajtów
długość stołu długość tablicy dm-verity w bajtach 4 bajty
stół opisaną wcześniej tabelę dm-verity bajty długości tabeli
wyściółka ta struktura ma długość od 0 do 32 tys. 0

Optymalizacja dm-verity

Aby uzyskać najlepszą wydajność z 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.