<form id="dlljd"></form>
        <address id="dlljd"><address id="dlljd"><listing id="dlljd"></listing></address></address>

        <em id="dlljd"><form id="dlljd"></form></em>

          <address id="dlljd"></address>
            <noframes id="dlljd">

              聯系我們 - 廣告服務 - 聯系電話:
              您的當前位置: > 關注 > > 正文

              dm-verity-ramdisk文件系統校驗的解決方法

              來源:CSDN 時間:2023-02-15 11:20:40

              近期做的一個項目,由于客戶對安全性要求比較高,涉及到文件系統校驗的問題,起初是在ramdisk中掛載rootfs后對所有重要的文件檢查sha256,但是隨著rootfs的逐步增大,發現校驗花費的時間太長,竟然達到十幾秒,于是就想改用一種方案,首先想到的是整個rootfs校驗后在掛載,這樣肯定是比一個文件一個文件校驗要來得快些,但是項目中用的是nandflash,并不是EMMC,所以文件系統只能采用支持壞塊管理的ubifs,于是這里就存在問題了,ubi層負責邏輯塊到物理塊的映射,也就是說在物理存儲上塊不一定是邏輯連續的,這樣在ubifs還沒有掛載之前讀取整個鏡像然后整體校驗肯定是行不通的,一個解決辦法是讀取ubi卷設備而不是mtd設備,這樣的話是可行的,但是一種更好的辦法是采用dm-verity,但是這個是Linux項目,并不是Android項目,通過對dm-verity移植,這里記錄一下對dm-verity的理解。

              dm-verity是什么?


              【資料圖】

              它是dm(device mapper)的一個target,是一個虛擬塊設備,專門用于文件系統的校驗

              +------------------------------+

              |                fs                   |

              +------------------------------+

              |

              +-------------------------------+

              |         dm-verity               |

              +-------------------------------+

              |

              +-------------------------------+

              |            block driver        |

              +--------------------------------+

              |

              +-------------------------------+

              |               block device    |

              +-------------------------------+

              fs在掛載的時候直接指定dm-verity設備,也就是fs直接交互的設備是dm-verity,dm-verity調用真正的塊驅動去讀取對應的塊,并計算hash值和hash-tree中對應的hash值進行比較,如果相等,則說明塊沒有被篡改,返回塊數據給fs,如果不相等,則說明塊被篡改,根據mode是返回EIO,或者直接重啟。

              首先通過ioctl去crt(create)一個dm-verity設備,通過傳入參數指定這個創建的dm-verity設備的一些特性,傳入的參數包括verity-table,當然也可以使用命令直接創建:

              Set up a device:  # dmsetup create vroot --readonly --table \    "0 2097152 verity 1 /dev/sda1 /dev/sda2 4096 4096 262144 1 sha256 "\    "4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076 "\    "1234000000000000000000000000000000000000000000000000000000000000"

              verity-table的內容如下:

              40 def build_verity_table(block_device, data_blocks, root_hash, salt):41    table = "1 %s %s %s %s %s %s sha256 %s %s"42    table %= (  block_device,43                block_device,44                BLOCK_SIZE,45                BLOCK_SIZE,46                data_blocks,47                data_blocks + (METADATA_SIZE / BLOCK_SIZE),48                root_hash,49                salt)50    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,因為它比較簡單。

              dm-verity的工作原理

              通過前面的描述,很容易理解dm-verity的工作過程,就拿我這個項目來說,squashfs需要讀取某個塊時,調用dm-verity讀取對應的塊,dm-verity根據verity-table中block_device,調用block_device讀取對應的塊,讀取到塊的內容后dm-verity會算出塊的sha256,然后跟verity-hash-tree中相對應的hash值進行比較,如果相等,則說明該塊沒有被修改過,一切正常。

              為何dm-verify支持所有的文件系統?

              該項目在選用dm-verity之前,我一直都在懷疑dm-verity是否支持ubifs,通過前面的描述,如果你對dm-verity的工作原理足夠理解的話,你就會發現,dm-verity跟文件系統是無關的,只要文件系統是工作在塊設備之上的,所以ubifs是不可以的,工作在塊設備之上的文件系統都是可以的,dm-verity是對邏輯塊校驗hash值,產生hash-tree的時候也是根據文件系統鏡像來產生的(然后除ubifs之外,不存在邏輯塊的概念,但是可以類似將它看出邏輯塊直接等于物理塊),至于邏輯塊到物理塊直接是怎樣映射,dm-verity根本就不需要關心。

              dm-verity為何這么快?

              了解了原理之后這個就很好回答了,因為dm-verity并不需要在掛載前對所有的塊進行校驗,而是在使用的過程中用到哪個塊就校驗哪個塊的hash值,這樣對于像android一個分區幾個G來說優勢就顯得更加明顯了。

              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值也需要修改,也就是說根hash也需要修改,所以只需要一種機制能保證root-hash不被篡改就行了。

              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的位置?

              這是用戶空間(/system/core/fs_mgr/ )的工作,android的做法是通過讀取文件系統的超級塊(Superblock,簡稱SB),里面記錄了文件系統的大小,verity-table緊挨著文件系統鏡像之后

              3.簽名的key存放在哪里?

              這里指的是public key

              verity-table-metadata主要是為了校驗verity-table的合法性,android的格式為:

              def build_metadata_block(verity_table, signature):    table_len = len(verity_table)    block = struct.pack("II256sI", MAGIC_NUMBER, VERSION, signature, table_len)    block += verity_table    block = block.ljust(METADATA_SIZE, "\x00")    return block

              verity-table的校驗是在用戶空間(/system/core/fs_mgr/ )中完成的,校驗合法之后會將verity-table傳給kernel使用。

              上面說的這些只是Android的一套,自己實現的話沒必要完全按照它的來,比如說verity-table和hash-tree沒有必要放在分區中,可以放在ramdisk中,簽名和校驗RSA2014可以自己實現,public key存放的位置可以自己決定,如放在ramdisk中或放在OTP中。

              dm-verity異常處理

              dm-verity簽名校驗失敗后會怎么做呢?下面是Android的做法:

              在metadata分區中會記錄dm-verity的狀態,提示是否掛載,同時在dm-verity設備創建時也會指定mode,dm-verity在內核中塊hash校驗失敗后不同的mode表現的行為不一樣。

              // Verity modesenum 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};static int load_verity_table(struct dm_ioctl *io, char *name, uint64_t device_size, int fd, char *table,int mode){    ...    if (mode == VERITY_MODE_EIO) { //對于比較老的內核dm-verity驅動,是不支持mode的,當block hash校驗不過時總是cause an I/O error for corrupted blocks        // allow operation with older dm-verity drivers that are unaware        // of the mode parameter by omitting it; this also means that we        // cannot use logging mode with these drivers, they always cause        // an I/O error for corrupted blocks        strcpy(verity_params, table);    } else if (snprintf(verity_params, bufsize, "%s %d", table, mode) < 0) {                     return -1;    }    ...    ioctl(fd, DM_TABLE_LOAD, io);}

              指定mode后,kernel中碰到校驗不過的塊的處理:

              /* * Handle verification errors. */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;}

              記錄一下移植的過程中踩過的坑:

              在移植的過程中發現Android6.0是有bug的,在產生hash-tree的時候:

              image_size = os.stat(out_file).st_size

              由于img是sparse過后的,所以這里的大小肯定是不對的,正確的做法應該是先unsparse,然后再計算大小。

              另外這里采用的是ubiblock:

              ubiblock --create  /dev/ubi0_0

              mtd--->ubi------>ubi vol----->ubiblock

              另外還可以采用gluebi:

              mtd---->ubi---->ubi vol--->mtd--->mtdblock

              ubiblock比較簡單,缺點是只讀,在掛載時必須指定為只讀:

              mount -t squashfs  /dev/ubiblock0_0  /mnt  -o ro

              生成燒錄鏡像的過程:

              rootfs dir---------mksquash------------>rootfs.squashfs-----------ubinize-------------->rootfs.ubi

              把rootfs.ubi燒進去即可。

              最終實現的效果如下:

              責任編輯:

              標簽: 文件系統

              相關推薦:

              精彩放送:

              新聞聚焦
              Top 中文字幕在线观看亚洲日韩