實作 dm-verity

Android 4.4 以上版本透過選用的 device-mapper-verity (dm-verity) 核心功能支援驗證開機程序,該功能可對區塊裝置進行透明完整性檢查。dm-verity 有助於防止持續性 Rootkit 保留 Root 權限並危害裝置。這項功能可協助 Android 使用者確保裝置在啟動時,處於與上次使用時相同的狀態。

具有 root 權限的可能有害應用程式 (PHA) 可以隱藏在偵測程式中,或以其他方式隱藏自己。因為根目錄軟體通常比偵測器擁有更多權限,因此可以向偵測程式「說謊」。

您可以使用 dm-verity 功能查看區塊裝置 (檔案系統的基礎儲存層),並判斷該裝置是否符合預期的設定。這項作業會使用加密編譯雜湊樹狀結構。每個區塊 (通常為 4k) 都有一個 SHA256 雜湊。

由於雜湊值會儲存在網頁樹狀結構中,因此只有頂層「根」雜湊值必須可信任,才能驗證樹狀結構的其餘部分。修改任何區塊的能力,等同於破壞加密編譯雜湊。請參閱下圖,瞭解這個結構。

dm-verity-hash-table

圖 1. dm-verity 雜湊表

開機分割區包含公開金鑰,必須由裝置製造商在外部驗證。該金鑰用於驗證該雜湊的簽章,並確認裝置的系統分區受到保護且未變更。

作業

dm-verity 防護機制位於核心中。因此,如果根目錄軟體在核心啟動前危害系統,就會保留該存取權。為降低這類風險,大多數製造商會使用燒錄在裝置中的金鑰來驗證核心。裝置出廠後,這個金鑰就無法變更。

製造商會使用該金鑰驗證第一層 Bootloader 的簽名,而 Bootloader 會依序驗證後續層級的簽名、應用程式 Bootloader,最後是核心。每家希望利用驗證開機程序的製造商,都應具備驗證核心完整性的方法。假設核心已完成驗證,核心可以查看區塊裝置,並在掛載時驗證。

驗證區塊裝置的方法之一,是直接對其內容進行雜湊運算,並與儲存的值進行比較。不過,嘗試驗證整個區塊裝置可能需要較長的時間,並耗用大量裝置電力。裝置需要很長的時間才能啟動,然後在使用前電量會大幅耗盡。

相反地,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 表格,請參閱 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 對應表時使用。

建立 dm-verity 對應表

建立 dm-verity 對應表,用於識別核心的區塊裝置 (或目標) 和雜湊樹位置 (相同的值)。這項對應用於 fstab 產生和啟動。表格也會指出區塊大小和 hash_start,也就是雜湊樹的起始位置 (具體來說,是從圖片開頭算起的區塊編號)。

如需 Verity 目標對應表欄位的詳細說明,請參閱 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
version 用於設定中繼資料區塊的版本 4 個位元組 目前為 0
簽名 以 PKCS1.5 填補格式呈現的資料表簽章 256 個位元組
表格長度 dm-verity 資料表的長度 (以位元組為單位) 4 個位元組
桌子 先前所述的 dm-verity 表 表格長度位元組
padding 這個結構體會以 0 填補至 32k 長度 0

最佳化 dm-verity

如要讓 dm-verity 發揮最佳效能,請務必:

  • 在核心中,為 ARMv7 開啟 NEON SHA-2,並為 ARMv8 開啟 SHA-2 擴充功能。
  • 嘗試不同的預讀和 prefetch_cluster 設定,找出最適合裝置的最佳設定。