實施 dm-verity

Android 4.4 及更高版本通過可選的 device-mapper-verity (dm-verity) 內核功能支持 Verified Boot,該功能提供對塊設備的透明完整性檢查。 dm-verity 有助於防止持久的 rootkit 保持 root 權限並危害設備。此功能可幫助 Android 用戶確保在啟動設備時它處於與上次使用時相同的狀態。

具有 root 權限的潛在有害應用程序 (PHA) 可以隱藏在檢測程序之外,並以其他方式掩蓋自己。生根軟件可以做到這一點,因為它通常比檢測器具有更高的特權,從而使軟件能夠對檢測程序“撒謊”。

dm-verity 功能可讓您查看塊設備,即文件系統的底層存儲層,並確定它是否匹配其預期配置。它使用加密哈希樹來做到這一點。對於每個塊(通常為 4k),都有一個 SHA256 哈希。

因為哈希值存儲在頁面樹中,所以只有頂級“根”哈希必須被信任才能驗證樹的其餘部分。修改任何塊的能力等同於破壞加密哈希。有關此結構的描述,請參見下圖。

dm-verity-哈希表

圖 1. dm-verity 哈希表

引導分區中包含一個公鑰,必須由設備製造商進行外部驗證。該密鑰用於驗證該哈希的簽名並確認設備的系統分區受到保護且未更改。

手術

dm-verity 保護存在於內核中。因此,如果 root 軟件在內核啟動之前破壞了系統,它將保留該訪問權限。為了降低這種風險,大多數製造商使用刻錄到設備中的密鑰來驗證內核。一旦設備出廠,該密鑰就不可更改。

製造商使用該密鑰來驗證第一級引導加載程序上的簽名,進而驗證後續級別、應用程序引導加載程序以及最終內核上的簽名。每個希望利用驗證引導的製造商都應該有一種方法來驗證內核的完整性。假設內核已經過驗證,內核可以查看塊設備並在掛載時對其進行驗證。

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

相反,dm-verity 單獨驗證塊,並且僅在每個塊被訪問時驗證。當讀入內存時,該塊被並行散列。然後在樹上驗證哈希。而且由於讀取塊是一項非常昂貴的操作,因此這種塊級驗證引入的延遲相對來說是微不足道的。

如果驗證失敗,設備會生成一個 I/O 錯誤,指示無法讀取該塊。正如預期的那樣,它看起來好像文件系統已損壞。

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

前向糾錯

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

執行

概括

  1. 生成 ext4 系統映像。
  2. 為該圖像生成哈希樹
  3. 為該哈希樹構建一個 dm-verity 表
  4. 簽署該 dm-verity 表以生成表簽名。
  5. 將表簽名和 dm-verity 表捆綁到 verity 元數據中。
  6. 連接系統映像、verity 元數據和哈希樹。

有關哈希樹和 dm-verity 表的詳細說明,請參閱The Chromium Projects - Verified Boot

生成哈希樹

如簡介中所述,哈希樹是 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,哈希樹的起始位置(具體來說,它從圖像開始的塊號)。

有關驗證目標映射表字段的詳細說明,請參閱cryptsetup

簽署 dm-verity 表

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

要使用此簽名和密鑰組合驗證分區:

  1. 將 libmincrypt 兼容格式的 RSA-2048 密鑰添加到/verity_key/boot分區。識別用於驗證哈希樹的密鑰的位置。
  2. 在相關條目的 fstab 中,將verify添加到fs_mgr標誌。

將表簽名綁定到元數據中

將表簽名和 dm-verity 表捆綁到 verity 元數據中。整個元數據塊是版本化的,因此可以擴展,例如添加第二種簽名或更改某些順序。

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

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

十六進制的字節值是:

  • 第一個字節 = b0
  • 第二個字節 = 01
  • 第三個字節 = b0
  • 第四個字節 = 01

下圖描述了 Verity 元數據的細分:

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

此表描述了這些元數據字段。

表 1. Verity 元數據字段

場地目的尺寸價值
幻數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 設置,為您的設備找到最佳配置。