實施 dm-verity

Android 4.4 及更高版本透過可選的 device-mapper-verity (dm-verity) 核心功能支援驗證啟動,該功能提供區塊裝置的透明完整性檢查。 dm-verity 有助於防止持久性 Rootkit 保留 root 權限並危害裝置。此功能可協助 Android 使用者在啟動裝置時確保其處於與上次使用時相同的狀態。

具有 root 權限的潛在有害應用程式 (PHA) 可以隱藏檢測程式並以其他方式隱藏自身。生根軟體可以做到這一點,因為它通常比檢測器擁有更多特權,使軟體能夠對檢測程式「撒謊」。

dm-verity 功能可讓您查看區塊裝置(檔案系統的底層儲存層),並確定它是否與其預期設定相符。它使用加密哈希樹來完成此操作。對於每個區塊(通常為 4k),都有一個 SHA256 雜湊值。

由於雜湊值儲存在頁面樹中,因此只有頂級「根」雜湊必須可信才能驗證樹的其餘部分。修改任何區塊的能力相當於破壞加密哈希。請參閱下圖以了解此結構的描述。

dm 驗證哈希表

圖 1. dm-verity 哈希表

啟動分區中包含公鑰,該公鑰必須由設備製造商進行外部驗證。此金鑰用於驗證該雜湊的簽章並確認裝置的系統分割區受到保護且未變更。

手術

dm-verity 保護存在於核心中。因此,如果生根軟體在核心啟動之前破壞了系統,它將保留該存取權限。為了降低這種風險,大多數製造商使用刻錄到裝置中的金鑰來驗證核心。一旦設備出廠,該密鑰就無法更改。

製造商使用該金鑰來驗證第一級開機載入程式上的簽名,進而驗證後續等級、應用程式引導程式以及最終核心的簽名。每個希望利用驗證啟動的製造商都應該有驗證核心完整性的方法。假設核心已經過驗證,核心可以查看區塊裝置並在安裝時進行驗證。

驗證區塊設備的一種方法是直接散列其內容並將它們與儲存的值進行比較。然而,嘗試驗證整個區塊設備可能需要很長的時間並消耗設備的大量電量。設備需要很長時間才能啟動,然後在使用前會被大量耗盡。

相反,dm-verity 僅在存取每個區塊時單獨驗證區塊。當讀入記憶體時,該區塊會被並行散列。然後在樹上驗證哈希值。由於讀取區塊是一項非常昂貴的操作,因此這種區塊級驗證引入的延遲相對較小。

如果驗證失敗,裝置會產生 I/O 錯誤,指示無法讀取該區塊。正如預期的那樣,檔案系統似乎已損壞。

應用程式可以選擇在沒有結果資料的情況下繼續,例如當應用程式的主要功能不需要這些結果時。但是,如果應用程式在沒有資料的情況下無法繼續,它將失敗。

前向糾錯

Android 7.0 及更高版本透過前向糾錯 (FEC) 提高了 dm-verity 的穩健性。 AOSP 實作從常見的里德-所羅門糾錯碼開始,並應用一種稱為交織的技術來減少空間開銷並增加可恢復的損壞區塊的數量。有關 FEC 的更多詳細信息,請參閱帶有糾錯功能的嚴格強制驗證啟動

執行

概括

  1. 產生 ext4 系統映像。
  2. 為該圖像生成哈希樹
  3. 為該哈希樹建立一個 dm-verity 表
  4. 對該 dm-verity 表進行簽名以產生表簽名。
  5. 將表簽章和 dm-verity 表捆綁到 verity 元資料中。
  6. 連接系統映像、驗證元資料和雜湊樹。

有關雜湊樹和 dm-verity 表的詳細說明,請參閱Chromium 專案 - 驗證啟動

產生哈希樹

如簡介所述,哈希樹是 dm-verity 的組成部分。 cryptsetup工具將為您產生一個哈希樹。或者,這裡定義一個相容的:

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

為了形成雜湊值,系統映像在第 0 層被分割為 4k 區塊,每個區塊分配一個 SHA256 雜湊值。第 1 層是透過僅將那些 SHA256 雜湊值加入到 4k 區塊中而形成的,從而產生較小的影像。第 2 層的構成與第 1 層的 SHA256 雜湊值相同。

這樣做直到前一層的 SHA256 雜湊值可以容納在單一區塊中。當獲得該區塊的 SHA256 時,您就擁有了樹的根雜湊值。

哈希樹的大小(以及對應的磁碟空間使用量)會隨驗證分割區的大小而變化。在實踐中,哈希樹的大小往往很小,通常小於 30 MB。

如果層中有一個區塊沒有完全被前一層的雜湊值自然填充,則應該用零填充它以達到預期的 4k。這可以讓您知道哈希樹尚未被刪除,而是用空白資料完成。

若要產生哈希樹,請將第 2 層雜湊值連接到第 1 層雜湊值,將第 3 層雜湊值連接到第 2 層雜湊值,依此類推。將所有這些寫到磁碟上。請注意,這並沒有引用根哈希的第 0 層。

回顧一下,建構哈希樹的一般演算法如下:

  1. 選擇隨機鹽(十六進位編碼)。
  2. 將系統映像解分成 4k 區塊。
  3. 對於每個區塊,取得其(加鹽的)SHA256 雜湊值。
  4. 連接這些哈希值以形成一個級別
  5. 用 0 將等級填入 4k 塊邊界。
  6. 將層級連接到哈希樹。
  7. 使用上一個層級作為下一個層級的來源重複步驟 2-6,直到只有一個雜湊。

其結果是一個哈希值,即您的根哈希值。在建立 dm-verity 映射表期間會使用此值和您的 salt。

建構 dm-verity 映射表

建立 dm-verity 映射表,該表標識核心的區塊裝置(或目標)和哈希樹的位置(具有相同的值)。此映射用於fstab產生和引導。表格也標識了區塊的大小和 hash_start,即哈希樹的起始位置(具體來說,是從影像開頭算起的區塊號)。

有關 verity 目標映射表欄位的詳細說明,請參閱cryptsetup

簽 dm-verity 表

對 dm-verity 表進行簽名以產生表簽名。驗證分區時,首先驗證表格簽名。這是針對固定位置的啟動映像上的金鑰完成的。密鑰通常包含在製造商的建置系統中,以便自動包含在固定位置的裝置上。

若要使用此簽章和金鑰組合驗證分割區:

  1. 將 libmincrypt 相容格式的 RSA-2048 金鑰新增至/boot分區的/verity_key 。確定用於驗證哈希樹的密鑰的位置。
  2. 在相關條目的 fstab 中,將verify新增至fs_mgr標誌。

將表簽名捆綁到元資料中

將表簽章和 dm-verity 表捆綁到 verity 元資料中。整個元資料塊都經過版本控制,因此可以對其進行擴展,例如添加第二種簽名或更改某些順序。

作為健全性檢查,幻數與每組表元資料相關聯,以協助識別表。由於長度包含在 ext4 系統映像標頭中,因此這提供了一種在不知道資料本身內容的情況下搜尋元資料的方法。

這可確保您沒有選擇驗證未經驗證的分割區。如果是這樣,缺少這個幻數將停止驗證過程。這個數字類似:
0xb001b001

十六進位位元組值是:

  • 第一個位元組 = b0
  • 第二個位元組 = 01
  • 第三個位元組 = b0
  • 第四個位元組 = 01

下圖描述了驗證元資料的細分:

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

該表描述了這些元資料欄位。

表 1.驗證元資料字段

場地目的尺寸價值
幻數由 fs_mgr 用作健全性檢查4位元組0xb001b001
版本用於對元資料塊進行版本控制4位元組目前 0
簽名PKCS1.5 填充形式的表格簽名256字節
工作台長度dm-verity 表的長度(以位元組為單位) 4位元組
桌子前面描述的 dm-verity 表表格長度位元組
填充此結構的長度以 0 填充至 32k 0

優化 dm-verity

為了獲得 dm-verity 的最佳性能,您應該:

  • 在核心中,為 ARMv7 開啟 NEON SHA-2,為 ARMv8 開啟 SHA-2 擴充。
  • 嘗試不同的預讀和 prefetch_cluster 設置,找到適合您設備的最佳配置。