一、Device Mapper簡介
dm-verity是內核子系統的Device Mapper中的一個子模塊,所以在介紹dm-verity之前先要介紹一下Device Mapper的基礎知識。Device Mapper為Linux內核提供了一個從邏輯設備到物理設備的映射框架,通過它,用戶可以定制資源的管理策略。當前Linux中的邏輯卷管理器如LVM2(Linux Volume Manager 2)、EVMS(Enterprise Volume Mageagement System)、dmraid等都是基於該機制實現的。
Device Mapper有三個重要的概念:映射設備(Mapped Device)、映射表、目標設備(Target Device);映射設備是一個邏輯塊設備,用戶可以像使用其他塊設備那樣使用映射設備。映射設備通過映射表描述的映射關系和目標設備建立映射。對映射設備的讀寫操作最終要映射成對目標設備的操作。而目標設備本身不一定是一個實際的物理設備,它可以是另一個映射設備,如此反復循環,理論上可以無限迭代下去。映射關系本質上就是表明映射設備中的地址對應到哪個目標設備的哪個地址。
Device Mapper是一個靈活的架構,映射設備映射一個或多個目標設備,每個目標設備屬於一個類型,類型不同,對I/O處理不同,構造目標設備的方法也不同。映射設備可以映射多個不同類型的目標設備。
Dm-verity規定只能有兩個目標設備,一個是數據設備(Data Device),另一個是哈希設備(Hash Device);
Device Mapper可參考IBM博客:https://www.ibm.com/developerworks/cn/linux/l-devmapper/
二、dm-verity簡介
dm-verity是Device mapper架構下的一種目標設備類型,通過它來保障設備或設備分區的完整性,它的典型架構是如圖一。
dm-verity類型的設備需要兩個底層設備,一個是數據設備,顧名思義是用來存儲數據,實際上就是要保障完整性的設備,另一個是哈希設備,用來存儲哈希值,在校驗數據設備完整性時需要。圖中映射設備和目標設備是一對一關系,對映射設備的讀操作被映射成對目標設備的讀操作,在目標設備中,dm-verity又將讀操作映射為數據設備(Data Device)的讀操作。但是在讀操作的結束處,dm-verity加了一個額外的校驗操作,對讀到的數據計算一個hash值,用這個哈希值和存儲在哈希設備(Hash Device)中的值做比較,如果不同,則本次讀操作被標記為錯誤。
假設數據設備和哈希設備中每塊大小均為4KB,再假設使用hash算法SHA256,即每塊數據的哈希值為32B(256bits),則哈希設備中的每塊(4KB)存儲有4096/32=128個哈希值。所以在layer0中一個哈希設備的塊對應數據設備的128個塊。到這里似乎完整了,數據設備中存儲數據,哈希設備存儲哈希值。在讀取數據時,dm-verity還要防備哈希設備中存儲的哈希值被篡改的情況。所以要加上layer1,在layer1中的每塊數據對應layer0的128個塊,layer1中的數據就是對layer0中的數據(hash設備和數據設備中的數據)計算hash值,如果layer1中只有一塊,那么就此停止,否則繼續增加layer,直到layer n只有一塊。最后對layer n再計算hash值,稱這個hash值為root hash。這個root hash就可以反應數據設備和hash設備的變化。通過驗證root hash 就可以校驗數據是否被篡改。
(圖一) (圖二)
三、dm-verity后續
1. dm-verity是什么 ?
它是dm(device mapper)的一個target,是一個虛擬塊設備,專門用於文件系統的校驗。fs在掛載的時候直接指定 dm-verity 設備,也就是fs直接交互的設備是 dm-verity,dm-verity 調用真正的塊驅動去讀取對應的塊,並計算hash值和hash-tree中對應的hash值進行比較,如果相等,則說明塊沒有被篡改,返回塊數據給fs,如果不相等,則說明塊被篡改,根據mode是返回EIO,或者直接重啟。
fs | dm-verity | block driver | block device
首先通過ioctl去crt(create)一個dm-verity設備,通過傳入參數指定這個創建的dm-verity設備的一些特性,傳入的參數包括verity-table,verity-table的內容如下:
def build_verity_table(block_device, data_blocks, root_hash, salt): table = "1 %s %s %s %s %s %s sha256 %s %s" table %= ( block_device, block_device, BLOCK_SIZE, BLOCK_SIZE, data_blocks, data_blocks + (METADATA_SIZE / BLOCK_SIZE), root_hash, salt) return table
block_device描述了該dm-verity設備對應了那個底層的塊設備,第二個block_device指定了hash-tree存在於哪個塊設備上,對於我這個項目就是/dev/ubiblock0_0,BLOCK_SIZE描述了多大一個塊對應一個hash,一般都4k, data_blocks描述了有多少個4k的塊,data_blocks + (METADATA_SIZE / BLOCK_SIZE) 表示hash-tree在對應塊設備上的偏移,由此來找到hash-tree,root_hash為hash-tree的根hash。
dm-verity工作在塊設備之上,所以這里是/dev/ubiblock0_0,於是就不能再用ubifs 了,因為ubifs工作在卷設備之上,而/dev/ubi0_0是一個字符設備,所以只能采
用工作在塊設備之上的文件系統,我這里采用了squashfs,因為它比較簡單。
2. dm-verity的工作原理
squashfs需要讀取某個塊時,調用dm-verity讀取對應的塊,dm-verity根據verity-table中block_device,調用block_device讀取對應的塊,讀取到塊的內容后dm-verity會算出塊的sha256,然后跟verity-hash-tree中相對應的hash值進行比較,如果相等,則說明該塊沒有被修改過,一切正常。
3. dm-verify支持所有的文件系統的原因
dm-verity跟文件系統是無關的,只要文件系統是工作在塊設備之上的,所以ubifs是不可以的,工作在塊設備之上的文件系統都是可以的,dm-verity是對邏輯塊校驗hash值,產生hash-tree的時候也是根據文件系統鏡像來產生的(然后除ubifs之外,不存在邏輯塊的概念,但是可以類似將它看出邏輯塊直接等於物理塊),至於邏輯塊到物理塊直接是怎樣映射,dm-verity根本就不需要關心。
4. dm-verity比較快的原因
因為dm-verity並不需要在掛載前對所有的塊進行校驗,而是在使用的過程中用到哪個塊就校驗哪個塊的hash值,這樣對於像android一個分區幾個G來說優勢就顯得更加明顯了。
5.dm-verity是如何保證安全的
前面說過每個block都在hash-tree中記錄了對應的hash值,這樣就能防止別人篡改block的內容了,但是如果黑客把block改了之后,重新計算hash把hash-tree中對應的hash值也改了呢,這樣就能神不知鬼不覺了,所以必須要有一種機制防止hash-tree被篡改,hash-tree是這樣一種結構,所有的數據block對應的hash值放在最底層,也就是第0層,第1層的hash值由下面一層的hash值計算得到,除了第0層,其他的層hash值都不對應物理上block的hash值,它們存在的意義只是為了構建hash鏈,防止hash篡改,這樣第0層的hash值改變了的話,上層對應的hash值也需要修改,也就是說root-hash也需要修改,所以只需要一種機制能保證root-hash不被篡改就行了。
6. verity-table的校驗
Android中采用的方法是算root-hash的簽名,verity-table中保存了root-hash,對verity-table進行簽名,在Android中,系統進入ramdisk后,由/system/core/fs_mgr/
負責dm-verity設備的創建。verity-table的校驗,這里涉及到的一些知識是:
(1) 如何知道哪些分區需要校驗?
fs_mgr通過讀取fstab文件,其中記錄了哪些分區需要校驗
(2) 如何知道需要校驗的分區中verity-table的位置?
這是通過讀取文件系統的超級塊(Superblock,簡稱SB),里面記錄了文件系統的大小,verity-table緊挨着文件系統鏡像之后。
(3) 簽名的key存放在哪里?
這里指的是public key,上面說的這些只是Android的一套,自己實現的話沒必要完全按照它的來,比如說verity-table和hash-tree沒有必要放在分區中,可以放在ramdisk中,簽名和校驗RSA2014可以自己實現,public key存放的位置可以自己決定,如放在ramdisk中或放在OTP中。
7. dm-verity異常處理
dm-verity簽名校驗失敗后會怎么做呢?下面是Android的做法:
在metadata分區中會記錄dm-verity的狀態,提示是否掛載,同時在dm-verity設備創建時也會指定mode,dm-verity在內核中塊hash校驗失敗后不同的mode表現的行為不一樣。mode如下:
enum verity_mode { VERITY_MODE_EIO = 0, VERITY_MODE_LOGGING = 1, VERITY_MODE_RESTART = 2, VERITY_MODE_LAST = VERITY_MODE_RESTART, VERITY_MODE_DEFAULT = VERITY_MODE_RESTART };
指定mode后,kernel中碰到校驗不過的塊的處理:
static int verity_handle_err(struct dm_verity *v, enum verity_block_type type, unsigned long long block) { ... out: if (v->mode == DM_VERITY_MODE_LOGGING) return 0; if (v->mode == DM_VERITY_MODE_RESTART) kernel_restart("dm-verity device corrupted"); return 1; }
參考:
深入理解dm-verity機制:https://blog.csdn.net/whuzm08/article/details/85272419
dm-verity:https://www.cnblogs.com/linhaostudy/archive/2018/01/17/7418289.html
android dm-verity 功能:https://blog.csdn.net/ee230/article/details/73348344?locationNum=6&fps=1 (有校驗步驟,比較好)