Notes about Android dm-verity
Notes about Android 6.0 dm-verity, be aware that there might be some misunderstandings.
目录
Device-Mapper概念
build verity
setup verity
io routine
一. Device-Mapper概念
从下面左图我们可以看到 mapped device1 通过映射表和 a、b、c 三个 target device 建立了映射关系,而 target device a 又是通过 mapped device 2 演化过来,mapped device 2 通过映射表和 target device d 建立映射关系。
代码实现的结构模型如右图,mapped_device指代左图的Mapped Device,dm_table与其指向的dm_target数组指代左图中的mapping table。_targets是一个target_type的链表。Target_type 结构主要包含了 target device 对应的 target driver 插件的名字、定义的构建和删除该类型target device的方法、该类target device对应的IO请求重映射和结束IO的方法等。
以dm-verity为例,概念模型如下。
二. build verity
1. build_verity_tree
涉及到的文件:/local/sdb/work_area/pixi45/system/extras/verity/build_verity_tree.cpp
编译过程在调用make_ext4fs创建system.img后,会调用build_verity_tree程序创建system.img的verity_tree,如下:
格式如下:
build_verity_tree –A
过程:
a) 根据block_size大小和hash_size大小,计算得到一个block可以存放的hash个数
从level 0开始,计算需要多少个level。
创建数组verity_tree_levels, 用于指示每一个level在verity_tree中的起始地址
创建数组verity_tree_level_blocks, 用于指示每一个level在verity_tree中的长度
b) 计算ext4 image中各个块的hash值存到verity_tree的level0,然后逐层往上计算各层的hash值,并填入verity_tree中相应的level
2. build_verity_metadata
涉及到的文件:/local/sdb/work_area/pixi45/system/extras/verity/build_verity_metadata.py
编译过程在调用build_verity_tree程序创建system.img的verity_tree后,会调用build_verity_metadata.py程序创建system.img的verity_metadata.img
格式如下:
Build_verity_metadata.py <data_image_blocks> <metadata_image> <root_hash> <block_device> <signer_path> <signing_key>
生成的metadata结构如下:
三. setup verity
在执行fs_mgr_mount_all时,遇到enable dm-verity的分区,会调用fs_mgr_setup_verity
如果setup verity不成功,则不会mount该分区
下面描述fs_mgr_setup_verity的过程
- read_verity_metadata
读出附在镜像尾部的verity_metadata.img
- create_verity_device
创建dm-verity设备
fd = open("/dev/device-mapper", O_RDWR)
ioctl(fd, DM_DEV_CREATE, io)
md驱动相应处理:
a) 初始化mapped_device结构md,以"dm-%d"为名字创建块设备
b) 设置块设备的构造请求函数blk_queue_make_request(md->queue, dm_request)。dm_request构造请求函数处理bio重定向转发,发往/dev/dm-0的IO请求将被转发到//dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system
c) 初始化hash_cell节点hc,使hc的md指针指向md,根据参数name/uuid计算hash得到在_name_bucket/_uuid_bucket中的index, 将该节点链入_name_bucket和_uuid_bucket相应index下的链表。_name_bucket/_uuid_bucket用于后续快速通过name/uuid找到hc,然后通过hc->md找到md。
d) 使md的interface_ptr指向hc。
- get_verity_device_name
ioctl(fd, DM_DEV_STATUS, io)
dev_num = (io->dev & 0xff) | ((io->dev >> 12) & 0xfff00)
asprintf(dev_name, “/dev/block/dm-%u”, dev_num)
- verify_table
使用/verity_key验证verity_metadata.img中的table的签名
- load_verity_state
如果如下文件中存在““dm-verity device corrupted””
则将verity state设置为VERITY_MODE_LOGGING
“/sys/fs/pstore/console-ramoops”,
“/proc/last_kmsg”,
否则校验签名,正确则为VERITY_MODE_DEFAULT
否则为VERITY_MODE_EIO
- DM_TABLE_LOAD
构造如下参数[dm_ioctl | dm_target_spec | table mod],即如下,蓝色部分为dm_ioctl , 绿色部分为dm_target_spec,橙色部分为table和mode。
将该参数传入ioctl(fd, DM_TABLE_LOAD, io)
a) 根据param中的name/uuid找到md
b) 创建dm_table结构体,并根据table参数初始化dm_targets数组,并调用tgt->type->ctr(tgt, argc, argv)
c) 根据param中的target_type字符串在_targets中找到对应的target_type,使dm_target的type指针指向该target_type 。_targets数组的内容是每个dm target driver在注册时通过dm_register_target注册到该数组的
d) Dm_table创建io_pool以及bs内存池,bs内存池的front_pad为如下结构。
在对bio进行拆分转发时,需要构造dm_target_io结构体,此时会用到front_pad中的dm_target_io[:clone)。
在调用target_type的map()函数进行转发时,bio中需要携带的私有信息可以存放在per_bio_data中
dm_verity相关
对于dm-verity,tgt->type->ctr(tgt, argc, argv)会调用verity_ctr,
创建dm_verity结构体,将存入dm_target的private字段
ti->private = v;
解析传入的参数,保存到v
dm_bufio_client_create
- DM_DEV_SUSPEND
ioctl(fd, DM_DEV_SUSPEND, io)
驱动层调用dm_resume
dm_swap_table(md, new_map);//设置md->map为dm_table
将md->io_pool和md->bs指向dm_table申请的io_pool和bs
8.BLKROSET
open(blockdev, O_RDONLY | O_CLOEXEC)
ioctl(fd, BLKROSET, &ON)
设置block device为只读
四. io routine
- bio的转发
由于fs_mgr_setup_verity将fstab->blk_device修改为” /dev/dm-0”, 后续mount system分区的操作是mount /dev/dm-0这个设备, 读写io也是先发往这个设备
fstab->blk_device = verity_blk_name;// /dev/dm-0
dm设备在初始化时设置了构造请求函数dm_request
dm_init_md_queue
->
blk_queue_make_request(md->queue, dm_request);
发给/dev/dm-0的IO请求会在构造请求函数dm_request中进行转发处理。
dm_request返回值如下,分别有如下含义:
#define DM_MAPIO_SUBMITTED 0//已有target处理,不需要再次转发
#define DM_MAPIO_REMAPPED 1//已经对目标设备和目标扇区区间重映射,再次转发
#define DM_MAPIO_REQUEUE//重新发送
- verity_map()过程
a) 验证bio要读取的扇区地址和大小是否都是以block_size(4K)对齐的
b) 重映射bio的目标设备及其目标扇区
由于v->data_dev->bdev在verity_ctr()中被设置为传入的参数,即/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system, 所以bio会转发给system分区块设备
从ti->private得到dm_verity结构体v,v是在verity_ctr()初始化的
其中v->data_dev->bdev被初始化为传入的参数,即/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system, 所以bio会转发给system分区块设备
v->data_start初始化为0,v->ti->begin在table_load的时候被初始化为参数中的dm_target_spec->sector_start, 即0,所以bio->bi_iter.bi_sector=0+bio->bi_iter.bi_sector-0, 即保持原来的值。
c) verity_prefetch_io将bio需要读取的块范围对应的verity_tree的块读取到cache
调用generic_make_request(bio)转发bio到system分区
return DM_MAPIO_SUBMITTED表示后续不在需要转发bio
d) verity_end_io()
在读取IO结束前,会调用verity_verify_io()验证块hash
对于bio中的每一块验证要读取的块在verity_tree中level0的hash值是否已经校验过,如果没有,从root开始,逐层校验。
计算bio中的块内容的hash值,然后将其与verity_tree中的hash值进行比较。
将验证结果通过bio_endio(bio, error)通知,成功返回0,否则返回-EIO