Wdrażanie dm-verity

Android 4.4 i nowsze obsługują Verified Boot dzięki opcjonalnej funkcji jądra device-mapper-verity (dm-verity), która zapewnia przejrzyste sprawdzanie integralności urządzeń blokowych. dm-verity pomaga zapobiegać uporczywym rootkitom, które mogą trzymać uprawnienia roota i narażać urządzenia. Ta funkcja pomaga użytkownikom Androida upewnić się, że podczas uruchamiania urządzenie jest 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 maskować się w inny sposób. 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 przyjrzeć się urządzeniu blokowemu, bazowej warstwie pamięci masowej systemu plików, i określić, czy pasuje do 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, tylko hash najwyższego poziomu „głównego” musi być zaufany, aby zweryfikować resztę drzewa. Możliwość modyfikacji któregokolwiek z bloków byłaby równoznaczna ze złamaniem kryptograficznego skrótu. Zobacz poniższy diagram, aby zobaczyć opis tej struktury.

dm-verity-hash-tabela

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. 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. Jeśli więc oprogramowanie do rootowania naruszy system przed pojawieniem się jądra, zachowa ten dostęp. Aby ograniczyć to ryzyko, większość producentów weryfikuje jądro za pomocą klucza wypalonego w urządzeniu. Klucza tego nie można zmienić po opuszczeniu fabryki przez urządzenie.

Producenci używają tego klucza do weryfikacji sygnatury na bootloaderze pierwszego poziomu, który z kolei weryfikuje sygnaturę na kolejnych poziomach, bootloaderze aplikacji i ostatecznie jądrze. Każdy producent chcący skorzystać ze zweryfikowanego rozruchu powinien mieć 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 montowania.

Jednym ze sposobów weryfikacji urządzenia blokowego jest bezpośrednie zaszyfrowanie jego zawartości i porównanie jej z przechowywaną wartością. Jednak próba sprawdzenia całego urządzenia blokowego może zająć dużo czasu i pochłonąć znaczną część energii urządzenia. Urządzenia uruchamiałyby się przez długi czas, a następnie byłyby znacznie rozładowywane przed użyciem.

Zamiast tego dm-verity weryfikuje bloki indywidualnie i tylko wtedy, gdy każdy z nich jest dostępny. Podczas wczytywania do pamięci blok jest mieszany równolegle. Hash jest następnie weryfikowany w górę drzewa. A ponieważ odczyt 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ć. Wygląda na to, że system plików został uszkodzony, zgodnie z oczekiwaniami.

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

Korekta błędów do przodu

Android 7.0 i nowszy poprawia niezawodność dm-verity dzięki korekcji błędów w przód (FEC). Implementacja AOSP zaczyna się od wspólnego kodu korekcji błędów Reeda-Solomona i stosuje technikę zwaną przeplataniem, aby zmniejszyć narzut 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 skrótów.
  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 verity 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 mieszającego

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, kompatybilny jest zdefiniowany tutaj:

<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 łączenie tylko tych skrótów SHA256 w bloki 4k, co daje znacznie mniejszy obraz. Warstwa 2 jest tworzona identycznie, z haszami SHA256 warstwy 1.

Dzieje się tak, dopóki skróty SHA256 poprzedniej warstwy nie zmieszczą się w jednym bloku. Kiedy otrzymasz SHA256 tego bloku, masz główny skrót 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 mieszających jest zwykle mały, często mniejszy niż 30 MB.

Jeśli masz blok w warstwie, który nie jest całkowicie naturalnie wypełniony przez skróty 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 jest uzupełnione pustymi danymi.

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

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

  1. Wybierz losową sól (kodowanie szesnastkowe).
  2. Rozdziel 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. Wypełnij 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, dopóki nie uzyskasz tylko jednego skrótu.

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

Budowanie tabeli odwzorowania dm-verity

Zbuduj tabelę 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 identyfikuje również rozmiar bloków i hash_start, początkową lokalizację drzewa skrótów (konkretnie numer jego bloku od początku obrazu).

Zobacz cryptsetup , aby zapoznać się ze szczegółowym opisem pól tabeli mapowania Verity Target.

Podpisywanie tabeli dm-verity

Podpisz tabelę dm-verity, aby utworzyć podpis tabeli. Podczas weryfikowania partycji najpierw sprawdzana jest poprawność podpisu tabeli. Odbywa się to w stosunku do klucza w obrazie rozruchowym w stałej lokalizacji. Klucze są zwykle zawarte w systemach kompilacji producentów w celu automatycznego dołączania na urządzeniach w stałej lokalizacji.

Aby zweryfikować partycję z tą kombinacją podpisu i klucza:

  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ótów.
  2. W fstab dla odpowiedniego wpisu dodaj verify do flag fs_mgr .

Łączenie podpisu tabeli w metadane

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 podpisu 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.

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

Wartości bajtów w heksach to:

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

Poniższy diagram przedstawia podział metadanych verity:

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

Ta tabela opisuje te pola metadanych.

Tabela 1. Pola metadanych Verity

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 sygnatura tabeli w formie wypełnionej PKCS1.5 256 bajtów
długość stołu długość tablicy dm-verity w bajtach 4 bajty
tabela tabela dm-verity opisana wcześniej Bajty długości tabeli
wyściółka ta struktura jest wypełniona 0 do 32k długości 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.