Android 中的dm-verity原理分析
[-]
- Android 中的Verified Boot之dm-verity
- 相关原理
- 为什么要使用dm-verity
- Dm-verity的工作流程
- Dm-verity的实现
- 接口
- Deveice Mapper框架
- Device mapper 框架下的dm-verity驱动实现
- 用户空间Dm-verity的使用
- 实现
- 相关原理
Android 中的Verified Boot之dm-verity
1.相关原理
Android中Verified Boot有分好多种,这里只涉及到了dm-verity这个东西,所以直接从dm-verity那里开始入手,至于bootloader等其他的就不多说。
为什么要使用dm-verity
Dm-verity的工作流程
Dm-verity的实现
def BuildImage(in_dir, prop_dict, out_file, target_out=None):......try:if reserved_blocks and fs_type.startswith("ext4"):(ext4fs_output, exit_code) = RunCommand(build_command)else:(_, exit_code) = RunCommand(build_command)......
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
可以在build脚本中通过RunCommand(build_command)生成了ext4的文件系统。至于这个build_command是什么有兴趣的朋友可以去看一看build_image.py,比较长,这里就不贴出来了。
Generate a hash tree for that image
def BuildVerityTree(sparse_image_path, verity_image_path, prop_dict):cmd = "build_verity_tree -A %s %s %s" % (FIXED_SALT, sparse_image_path, verity_image_path)print cmdstatus, output = commands.getstatusoutput(cmd)
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
在build脚本中有这么一段是描述了如何去构建hashtree的。实际上,这棵hash tree的作用就是用来描述system.img的变化。
通过build_verity_tree 这个程序去构建hash tree,所以构建hash tree 的算法就在这个build_verity_tree.cpp 文件中, 那么算法怎么实现的,就不去管它了。有点复杂。至于使用hash tree的原因官网上也提到了,最初他使用hash table来实现,后来数据太大之后,效率不好,所以使用hash tree,hash tree在处理大量数据的时候效率就非常高。产生的hash tree的结构就如下所示, 最后只有一个根hash,叶节点就是dm-verity所需要verify的分区的划分的一个个小块,它这里规定了每个块以4k的大小来划分。所以举个例子,要验证的system分区如果有800M,那么就有200万个块。所以说通过叶节点以及他的父hash到根hash就是描述了system.img的变化情况。这样的话用hash table来存效率就很差,所以使用hash tree来存速度更快。最后呢再主要保存这个root hash。
[ root ]/ . . . \[entry_0] [entry_1]/ . . . \ . . . \[entry_0_0] . . . [entry_0_127] . . . . [entry_1_127]/ ... \ / . . . \ / \blk_0 ... blk_127 blk_16256 blk_16383 blk_32640 . . . blk_32767
Build a dm-verity table for that hash tree
Sign that dm-verity table to produce a table signature.
Bundle the table signature and dm-verity table into verity metadata
def BuildVerityMetadata(image_size, verity_metadata_path, root_hash, salt,block_device, signer_path, key):cmd_template = ("system/extras/verity/build_verity_metadata.py %s %s %s %s %s %s %s")cmd = cmd_template % (image_size, verity_metadata_path, root_hash, salt,block_device, signer_path, key)print cmdstatus, output = commands.getstatusoutput(cmd)return True
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Ok,可以看到这段代码最终调用到了build_verity_metadata.py这个脚本。看一下他是如何建立metadata的。正好三部曲根上面的三部对应起来。
def build_verity_metadata(data_blocks, metadata_image, root_hash,salt, block_device, signer_path, signing_key):# build the verity tableverity_table = build_verity_table(block_device, data_blocks, root_hash, salt)# build the verity table signaturesignature = sign_verity_table(verity_table, signer_path, signing_key)# build the metadata blockmetadata_block = build_metadata_block(verity_table, signature)# write it to the outfilewith open(metadata_image, "wb") as f:f.write(metadata_block)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
看一下如何建立verity-table。实际上,verity-table就是为了去描述之前生成的hash tree。
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,root_hash,salt)return table
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
所以建立起来的verity table形如下面这样。说白了,verity-table只是一个描述hashtree的字符串,看一看他是如何描述hash tree的。下面这个例子选自Linux 文档,并非实际的system.img的hash tree的verity table。
第一个参数是版本,只有0和1,大多数情况下填1,不去深究。
第二个,第三个参数描述的是所保护的分区,这个例子中dm-verity保护的分区是/dev/sda1。
第四,第五个参数描述的该分区的每个block即hash tree的叶节点的大小,可以看到这里是4k,就是说,以4k为大小划分/dev/sda1为若干个区域。
第六,第七个参数描述了总共有多少个block,也就是hash tree有多少个叶节点。
第八个参数是hash 加密算法,这里使用sha256算法。
第九个参数是hash tree的根hash。
第十个参数是加密算法加的盐。
Ok,到这我们可以看到verity-table描述了叶节点和根hash以及hash的算法等。这样就通过一个字符串就把整棵树的形状就描绘出来了。
1 /dev/sda1 /dev/sda1 4096 4096 262144 262144 sha256 4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076
1234000000000000000000000000000000000000000000000000000000000000
verity table建立完后,对他进行签名,签名的格式可以参看Implementing verify boot那一章。签完名就把verity-table,签名信息和hash tree 一同写入到metadata中,最后返回给build脚本。
Concatenate the system image, the verity metadata, and the hash tree.
def BuildVerifiedImage(data_image_path, verity_image_path,verity_metadata_path):if not Append2Simg(data_image_path, verity_image_path,"Could not append verity tree!"):return Falseif not Append2Simg(data_image_path, verity_metadata_path,"Could not append verity metadata!"):return Falsereturn True
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
最后通过append2img这个命令吧metadata.img和metadata_verity.img附在system.img后面,这样就算是实现了dm-verity的metadata建立的过程。
至此,还未分析到流程图的具体流程的实现,那么在下一个段落开始就会去看一下流程图的具体实现。
2.接口
Deveice Mapper框架
......DM_VERSION //获取device Mapper的版本DM_TABLE_LOAD //向device mapper中load一个设备DM_TABLE_STATUS //获取device mapper的状态......
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
dm_ioctl这个结构体定义在dm-ioctl.h中。截取几个字段可以看一下,他主要是用于ioctl的时候传入给内核的参数的集合。其中io就是一个dm-ioctl的对象。用户空间使用ioctl的步骤是,先创建一个dm-ioctl和dm_target_spec对象,然后配置一下他们的参数,然后在dm_target_spec后面跟一个特定设备的特定参数(special param),将三者结合到dm-ioctl上,通过调用一下命令就可以在device mapper中load一个dm设备了。
ioctl(fd, DM_TABLE_LOAD, io)
- 1
- 1
struct dm_ioctl {__u32 version[3]; __u32 data_size; __u32 data_start; __u32 target_count; /* in/out */char name[DM_NAME_LEN]; /* device name */char uuid[DM_UUID_LEN]; /* unique identifier for* the block device */char data[7]; /* padding or data */
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
那么问题来了,ioctl是向device mapper发命令,如何去找到具体的dm设备呢。这个时候另外一个结构就出场了。同样定义在dm-ioctl.h中。
struct dm_target_spec {__u64 sector_start;__u64 length;__s32 status; char target_type[DM_MAX_TYPE_NAME];
};
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
可以看到他有四个字段,那最重要的是target_type[DM_MAX_TYPE_NAME]这个字符串,device mapper就是通过这个字符串从他的设备表中找到相应的dm设备。举个例子,我们这里需要找的是dm-verity,那么这个字符串就是”verity”。那么这个字符串必须是这个嘛?不是的,后面会讲这个字符串的起源地。
target_type 定义在include/linux/device-mapper.h 中,这个结构体就代表一个真实的dm设备。也就是说dm设备最终就是去实现这个接口就ok了,那么对于开发一个dm设备就非常简单了,只需要去实现这个接口,并向device mapper框架注册一把就可以了。
struct target_type {uint64_t features;const char *name;struct module *module;unsigned version[3];dm_ctr_fn ctr;dm_dtr_fn dtr;......dm_status_fn status;....../* For internal device-mapper use. */struct list_head list;};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
他里面罗列了许多字段,这里就列出了几个字段。ctr就是create函数,dtr:destory,跟Activity中的oncreate和ondestory简直一模一样。其中的name字段就是对应到之前dm_target_spec 的target_type字段。也就是说这个设备的名字的发源地就是在这。你这定义了“a”,那么在用户空间传参数的时候target_type就定义成“a”。
Device mapper 框架下的dm-verity驱动实现
static struct target_type verity_target = {.name = "verity",.version = {1, 2, 0},.module = THIS_MODULE,.ctr = verity_ctr,.dtr = verity_dtr,.map = verity_map,.status = verity_status,.ioctl = verity_ioctl,.merge = verity_merge,.iterate_devices = verity_iterate_devices,.io_hints = verity_io_hints,};static int __init dm_verity_init(void){int r;r = dm_register_target(&verity_target);if (r < 0)DMERR("register failed %d", r);return r;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
果然,在verity_target 中name字段定了成了verity,所以我们在用户空间设置dm_target_spec的target_type字段的时候就设置成verity。后面还要涉及到的一个重要的接口就是ctr,他在ioctl load table的时候会去调用到这个接口。当然这个verity_target 对象是在内核模块初始化的时候就被注册到device mapper中。
那么dm-verity的mode的改变是在什么时候进行呢?首先要知道dm-verity有三种工作模式,如下:
EIO:不挂载被破坏的分区。0
LOGGIN:忽略破坏的分区,继续挂载该分区。1
RESTART:发现分区被破坏,直接重启系统。2
那么暂且不谈用户空间如何去对dm-verity进行控制,先看在kernel里dm-verity状态的改变。dm-verity状态的改变是在dm-verity被load的时候发生的,dm-verity load一个dm设备的时候会调用该设备的target type的crt接口。在这里整理下dm-verity的调用流程。ioctl DM_TABLE_LOAD下去,调用到device mapper,device mapper通过传下来的的参数找到dm-verity,最终调用到ctr接口。
ioctl(fd, DM_TABLE_LOAD, io)—–>Device Mapper—通过target_type=“verity”–>dm-verity.ctr()
截取部分代码来看一看dm-verity中crt接口的实现。
static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
{/* Optional parameters */if (argc) {as.argc = argc;as.argv = argv;r = dm_read_arg_group(_args, &as, &opt_params, &ti->error);if (r)goto bad;while (opt_params) {opt_params--;opt_string = dm_shift_arg(&as);if (!opt_string) {ti->error = "Not enough feature arguments";r = -EINVAL;goto bad;}if (!strcasecmp(opt_string, DM_VERITY_OPT_LOGGING))v->mode = DM_VERITY_MODE_LOGGING;else if (!strcasecmp(opt_string, DM_VERITY_OPT_RESTART))v->mode = DM_VERITY_MODE_RESTART;else {ti->error = "Invalid feature arguments";r = -EINVAL;goto bad;}}}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
在上述代码之前,在crt函数里会先处理之前生成的verity-table,验证其有效性,具体可以参考源代码中完整的代码。那么从上述代码可以看到,dm-verity状态v->mode的改变是由传进去的特定参数决定,之前就讲到用户空间对dm-verity进行控制的话需要传三个东西进去,一个dm-ioctl,一个是dm_target_spec, 还有一个就是特定设备的特定参数,这个特定参数在这里就派上用场了。那么这个参数该如何定义呢,我们可以看一看dm-verity文档中的定义。
Construction Parameters
=============================================<version> <dev> <hash_dev><data_block_size> <hash_block_size><num_data_blocks> <hash_start_block><algorithm> <digest> <salt>[<#opt_params> <opt_params>]
在opt_params之前就是之前所提到的由build脚本生成的verity table正好能对起来。再来看一遍verity-table。
1 /dev/sda1 /dev/sda1 4096 4096 262144 262144 sha256 4392712ba01368efdf14b05c76f9e4df0d53664630b5d48632ed17a137f39076
1234000000000000000000000000000000000000000000000000000000000000
那么#opt_params是什么呢,#opt_params代表在verity table 后面需要跟的参数的个数,也就是argc,opt_params代表后面跟的参数,也就是argv,后面的参数有许多种,这里有可选参数的详细介绍。
根据kernel里crt函数中的实现看到,dm-verity对于opt_params一个一个遍历,然后做出相应的控制。dm-verity的mode是根据传进来的参数设置的。在dm-verity也只有在ctr这个地方才能去设置dm-verity的状态。当然如果不设置,他的默认mode是0,也就是EIO模式。
if (!strcasecmp(opt_string, DM_VERITY_OPT_LOGGING))v->mode = DM_VERITY_MODE_LOGGING;else if (!strcasecmp(opt_string, DM_VERITY_OPT_RESTART))v->mode = DM_VERITY_MODE_RESTART;
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
再看一下dm-verity中对应三种状态对是如何工作的。当dm-verity扫描出分区有损坏的时候,会触发dm-verity.c中一个回调函数–verity_handle_err。同样截取部分代码来看一看这个回调函数里如何处理不同的状态。
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
可以看到,如果是默认状态,就返回1,返回1之后,系统在mount的那边的代码就会不去挂载该被破坏的分区。如果是DM_VERITY_MODE_LOGGING的状态1,就返回0,那么系统在mount那块的代码就会忽略破坏,继续挂载该分区。如果是DM_VERITY_MODE_RESTART,就是状态2, 那么就重启系统。OK,至此就可以看到dm-verity中对于三种状态如何处理了。
OK,这里就解释了流程图这两段逻辑是如何实现的,图1中dm-verity在处于restart模式发生corruption之后重启的逻辑对应于
if (v->mode == DM_VERITY_MODE_RESTART)kernel_restart("dm-verity device corrupted");
- 1
- 2
- 1
- 2
图2中dm-verity在处于loggin模式发生corruption之后忽略corruption继续挂载系统的逻辑对应于
if (v->mode == DM_VERITY_MODE_LOGGING)return 0;
- 1
- 2
- 1
- 2
默认的EIO模式没在这张流程图里看到,显然google没有推荐使用这种模式。其实我觉得这种模式确实没什么用,这种模式在发生corruption之后直接选择不挂载,这样会导致系统hang住,也没什么意义。
用户空间Dm-verity的使用
static int load_verity_table(struct dm_ioctl *io, char *name, char *blockdev, int fd, char *table)
{char *verity_params;char *buffer = (char*) io;uint64_t device_size = 0;if (get_target_device_size(blockdev, &device_size) < 0) {return -1;}verity_ioctl_init(io, name, DM_STATUS_TABLE_FLAG);struct dm_target_spec *tgt = (struct dm_target_spec *) &buffer[sizeof(struct dm_ioctl)];// set tgt arguments hereio->target_count = 1;tgt->status=0;tgt->sector_start=0;tgt->length=device_size/512;strcpy(tgt->target_type, "verity");// build the verity params hereverity_params = buffer + sizeof(struct dm_ioctl) + sizeof(struct dm_target_spec);if (mode == VERITY_MODE_EIO) {// 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 blocksstrcpy(verity_params, table);} else {char *modeStr = mode == VERITY_MODE_LOGGING ? "ignore_corruption" : "restart_on_corruption";if (snprintf(verity_params, bufsize, "%s %d %s", table, 1, modeStr) < 0) {return -1;}}// set next target boundaryverity_params += strlen(verity_params) + 1;verity_params = (char*) (((unsigned long)verity_params + 7) & ~8);tgt->next = verity_params - buffer;// send the ioctl to load the verity tableif (ioctl(fd, DM_TABLE_LOAD, io)) {ERROR("Error loading verity table (%s)", strerror(errno));return -1;}return 0;
}static int resume_verity_table(struct dm_ioctl *io, char *name, int fd)
{verity_ioctl_init(io, name, 0);if (ioctl(fd, DM_DEV_SUSPEND, io)) {ERROR("Error activating verity device (%s)", strerror(errno));return -1;}return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
根据之前讲的,在device mapper框架下使用dm设备首先要创建一个dm-ioctl对象,并且初始化了这个对象。这个函数就在fs_mgr_verity.c 中可以找到。
verity_ioctl_init(io, name, DM_STATUS_TABLE_FLAG);
- 1
- 1
紧接着创建一个dm_target_spec对象,把它append在dm-iotcl的后面,初始化一下他的字段,其中可以看到将target_type设置成verity,这样通过ioctl就可以在device mapper框架下找到dm-verity这个设备了。
struct dm_target_spec *tgt = (struct dm_target_spec *) &buffer[sizeof(struct dm_ioctl)];// set tgt arguments hereio->target_count = 1;tgt->status=0;tgt->sector_start=0;tgt->length=device_size/512;strcpy(tgt->target_type, "verity");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这个时候已经完成了ioctl dm-verity的两部曲了,最后一步就是加上特定设备的特定参数,在这里就是dm-verity需要的参数。将verity table,opt_params和mode值拼接给
verity_params,就是之前讲的在dm-verity的kernel中的特定设备的参数。
char *modeStr = mode == VERITY_MODE_LOGGING ? "ignore_corruption" : "restart_on_corruption";if (snprintf(verity_params, bufsize, "%s %d %s", table, 1, modeStr) < 0) {return -1;}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
我在这就不详细介绍了,那么我后面需要用到两个参数,一个是ignore_corruption,还有一个restart_on_corruption,这两个值分别代表了之前提到的dm-verity的loggin mode 和 enforcing mode/restart mode。这样我们就可以在用户空间控制dm-verity的运行模式了。这两种不同的参数可以对应到之前kernel里dm-verity ctr中的相应逻辑。
至此,就解释了如何在用户空间设置dm-verity,也就解释了流程图中Setup dm-verity这段逻辑
3.实现
diff --git a/device.mk b/device.mk
index 3e14931..fe5ed92 100644
--- a/device.mk
+++ b/device.mk
@@ -336,6 +336,10 @@PRODUCT_SYSTEM_VERITY_PARTITION := /dev/block/platform/msm_sdcc.1/by-name/system$(call inherit-product, build/target/product/verity.mk)+PRODUCT_PACKAGES += \
+ slideshow \
+ verity_warning_images
+# setup scheduler tunablePRODUCT_DEFAULT_PROPERTY_OVERRIDES += \ro.qualcomm.perf.cores_online=2
diff --git a/fstab.shamu b/fstab.shamu
index 4a9989a..f07daf8 100644
--- a/fstab.shamu
+++ b/fstab.shamu
@@ -3,7 +3,7 @@# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK##<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
-/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro,barrier=1 wait
+/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro,barrier=1 wait,verify=/dev/block/platform/msm_sdcc.1/by-name/metadata/dev/block/platform/msm_sdcc.1/by-name/userdata /data ext4 rw,nosuid,nodev,noatime,nodiratime,noauto_da_alloc,nobarrier wait,check,formattable,forceencrypt=/dev/block/platform/msm_sdcc.1/by-name/metadata/dev/block/platform/msm_sdcc.1/by-name/cache /cache ext4 rw,noatime,nosuid,nodev,barrier=1,data=ordered wait,check,formattable/dev/block/platform/msm_sdcc.1/by-name/modem /firmware ext4 ro,barrier=1,context=u:object_r:firmware_file:s0 wait
diff --git a/init.shamu.rc b/init.shamu.rc
index 3513760..f6ebbf7 100644
--- a/init.shamu.rc
+++ b/init.shamu.rc
@@ -25,6 +25,9 @@chown system system /sys/kernel/debug/kgsl/procon init
+ # Load persistent dm-verity state
+ verity_load_state
+mkdir /oem 0550 root root# Set permissions for persist partition
@@ -60,6 +63,12 @@setprop persist.data.wda.enable truesetprop persist.data.df.agg.dl_pkt 10setprop persist.data.df.agg.dl_size 4096
+
+ # Adjust parameters for dm-verity device
+ write /sys/block/dm-0/queue/read_ahead_kb 2048
+
+ # Update dm-verity state and set partition.*.verified properties
+ verity_update_stateon post-fs-datamkdir /tombstones/modem 0771 system system
@@ -568,6 +577,9 @@on property:vold.decrypt=trigger_reset_mainstop gnss-svcd+on verity-logging
+ exec u:r:slideshow:s0 -- /sbin/slideshow warning/verity_red_1 warning/verity_red_2
+
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
先看patch中fstab一段,在system分区的fs_mgr_flags中添加
verify=/dev/block/platform/msm_sdcc.1/by-name/metadata,那么这个path表示什么呢?这个位置用于保存dm-verity的mode。也就是在流程图中,dm-verity重启之后怎么知道要进入loggin mode 的,就是将这个值存在了这个metadata分区中,metadata分区并不是之前所说的metadata.img,metadata.img是append在system.img之后的,也就是metadata.img是在system分区下,需要注意。
-/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro,barrier=1 wait
+/dev/block/platform/msm_sdcc.1/by-name/system /system ext4 ro,barrier=1 wait,verify=/dev/block/platform/msm_sdcc.1/by-name/metadata
fstab的这个patch的工作原理非常简单,如果不加这个地址的话,那么dm-verity会一直工作在EIO状态下,在这个状态下,如果dm-verity检测出分区有毛病,他就不会挂载分区,不会重启和忽略崩溃的分区。那么我们就来看一看如果不加地址他为什么只会工作在EIO状态下,导致流程不会按照既定的流程进行呢?这个不属于这个patch之内实现的东西,但其实也是实现dm-verity流程图相当重要的一部分,所以还是拿出来分析一下。真正设置dm-verity工作模式的代码在fs_mgr_verity.c中的fs_mgr_setup_verity这个函数。不贴出所有代码了,只截取一些关键的代码来分析一下。
if (load_verity_state(fstab, ¶ms.mode) < 0) {/* if accessing or updating the state failed, switch to the default* safe mode. This makes sure the device won't end up in an endless* restart loop, and no corrupted data will be exposed to userspace* without a warning. */params.mode = VERITY_MODE_EIO;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
进到load_verity_state函数中看一看
static int load_verity_state(struct fstab_rec *fstab, int *mode)
{return read_verity_state(fstab->verity_loc, offset, mode);
}
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
再往下调用到read_verity_state,可以看到第一个参数fstab->verity_loc,这个值就是之前从fstab里解析出来的verity后面的地址。如果fstab里没有verity或者verity后面灭有相应地址,那么这个值是null。
static int read_verity_state(const char *fname, off64_t offset, int *mode)
{fd = TEMP_FAILURE_RETRY(open(fname, O_RDONLY | O_CLOEXEC));if (fd == -1) {ERROR("Failed to open %s (%s)\n", fname, strerror(errno));goto out;}if (TEMP_FAILURE_RETRY(pread64(fd, &s, sizeof(s), offset)) != sizeof(s)) {ERROR("Failed to read %zu bytes from %s offset %" PRIu64 " (%s)\n",sizeof(s), fname, offset, strerror(errno));goto out;}*mode = s.mode;rc = 0;out:if (fd != -1) {close(fd);}return rc;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
可以看到如果路径不存在,直接报错,返回-1,此时load_verity_state(fstab, ¶ms.mode)就小于0,此时mode就设置为0。如果路径存在,ok,从中读取mode的值。
在回过头来看fs_mgr_setup_verity函数中拿到这个mode后干了些什么。
INFO("Enabling dm-verity for %s (mode %d)\n", mount_point, params.mode);// load the verity mapping tableif (load_verity_table(io, mount_point, verity.data_size, fd, ¶ms,format_verity_table) == 0) {goto loaded;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这时候load完mode之后就会走到上面这段代码中,可以看到最终调用到了load_verity_table这个函数。Ok, 这个函数我们之前在接口一章中提到了,去初始化dm_ioctl,dm_target_type两个结构体,并且绑定一个verity_params然后通过一个load table的ioctl命令传给kernel,去设置kernel中dm-verity的工作状态。kernel中dm-verity在handle error的时候就会根据不同的状态作出不同的动作。在上一个段落中已经分析了dm-verity.c中dm verity的ctr函数和handle error函数根据不同的mode如何工作的实现。再一次详细的解释了如何在用户空间实现流程图的这段逻辑。
接着看init.rc中的patch。
on init
+ # Load persistent dm-verity state
+ verity_load_stateon fs
+ # Update dm-verity state and set partition.*.verified properties
+ verity_update_state+on verity-logging
+ exec u:r:slideshow:s0 -- /sbin/slideshow warning/verity_red_1 warning/verity_red_2
+
第一段是在 init这个action中去调用这个verity_load_state,这个命令最终会调用到中builtins.cpp中的do_verity_load_state,里面去判断dm-verity的mode是不是loggin,如果是loggin in就会触发verity-logging这个action。这个action调用了slideshow程序。不熟悉的可以看一看init.rc的语法和init进程相关的原理。如果不是那么就走其他的流程。
看代码实现
static int do_verity_load_state(const std::vector<std::string>& args) {int mode = -1;int rc = fs_mgr_load_verity_state(&mode);if (rc == 0 && mode != VERITY_MODE_DEFAULT) {ActionManager::GetInstance().QueueEventTrigger("verity-logging");}return rc;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
那么do_verity_load_state完成的就是调用的fs_mgr_verity.c中的函数。这个函数就是根据fstab里那个地址结合一些pstore的状态去读取并设置dm-verity的工作模式。这段代码比较简单,不对他进行分析了。这段代码就解释了流程图中第一个判断dm-verity工作模式的判断逻辑。抛开具体代码,就看init.rc中的verity_load_state目的就是为了实现它
那么之前讲到trigger了verity-logging这个action,最后他就去调用到了slideshow 这个程序,那么其实也就是实现了上面这个判断框为N的时候的路径。通过打开slideshow这个app来警告用户system分区已经被破坏,是否要设置loggin模式继续挂载。
那么执行的slideshow程序的功能就是显示两幅警告的图片,一副用来提示你让你选择要不要继续mount文件系统,另一幅提示你你已经忽略警告了。其实这个slideshow只能算一个皮包公司,原生代码里面基本是什么都不干,就显示了两幅图片,但是厂商可以定制这个app,起到一些控制的效果,或者直接替换掉这个slideshow app,直接去实现一个类似recovery的界面都没问题。有兴趣的朋友可以去看一看/system/extras/slideshow/slideshow.cpp 是怎么写的,非常简单,就一个文件,代码也就100多行。slideshow最后实现效果图如下面这样。也就是对应与上面这段流程中的具体实现。
dm-verity的verified boot的流程就分析到这,还有许多不完善的地方,也有许多地方都略过代码分析,主要要把所有相关源代码拿出来分析的话,篇幅就太长,而且太过于繁琐,只是说大致的分析了了一遍dm-verity的那张流程图的实现,当然真要透彻的理解dm-verity的话肯定还是需要去仔细的研究dm-verity和fs_mgr的代码,在每一个段落的开头贴出了相关的代码文件可以参考。这也算是工作到现在唯一一次碰到的没有Java的关于Android的代码了,纪念一把。
转自http://blog.csdn.NET/u011280717/article/details/51867673
Android 中的dm-verity原理分析相关推荐
- 深入解析Android中View的工作原理
Android中的任何一个布局.任何一个控件其实都是直接或间接继承自View实现的,当然也包括我们在平时开发中所写的各种炫酷的自定义控件了,所以学习View的工作原理对于我们来说显得格外重要,本篇博客 ...
- android中倒计时控件CountDownTimer分析
android中倒计时控件CountDownTimer分析1 示例代码 new CountDownTimer(10000, 1000) {public void onTick(long millisU ...
- android scroller,深入理解Android中Scroller的滚动原理
View的平滑滚动效果 什么是实现View的平滑滚动效果呢,举个简单的例子,一个View从在我们指定的时间内从一个位置滚动到另外一个位置,我们利用Scroller类可以实现匀速滚动,可以先加速后减速, ...
- Android中app卡顿原因分析示例
http://www.cnblogs.com/zhucai/p/weibo-graphics-performance-analyse.html 朱才 专注于Android图形动画 MIUI工程师 博客 ...
- JDK 中的 BIO 实现原理分析(二十)
今天对JDK 中的 BIO 实现原理进行分析 一.简介 对比 Linux 上网络编程,我们会发现 JDK Socket 的编程逻辑是一模一样的.实际上也是这样,JDK 网络编程也没有做很多事,主要还是 ...
- Android 7.0 分屏原理分析
在以往的Android系统上,所有Activity都是全屏的,如果不设置透明效果,一次只能看到一个Activity界面. 但是从Android N(7.0)版本开始,系统支持了多窗口功能.在有了多窗口 ...
- 浅析 Android 中 Binder 的上层原理
纸上得来终觉浅,绝知此事要躬行 Binder 一直是我心里的一个坎儿,因为不管是 Android 中的哪个组件,都总是会或多或少的涉及 Binder.对于 Binder 是 Android 为了提升其 ...
- android viewgroup 事件,android中viewgroup的事件传递分析
在上一篇中我们分析了从view的dispatchTouchEvent到onTouchListener的onTouch回调到onTouchEvent到onClickLisener的onClickandr ...
- Android中启动Activity(startActivity)流程图分析
在上篇博文< Android中ActivityManagerService与应用程序(客户端)通信模型分析>中,我们从宏观架构上掌握 ActivityManagerService与应用程序 ...
- Android中图像变换Matrix的原理应用
第一部分 Matrix的数学原理 在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类.Android中的Matrix是一个3 x 3的矩阵,其内容如下: Matri ...
最新文章
- 轻断食是一种科学的减肥方法吗?
- XVI Open Cup named after E.V. Pankratiev. GP of Ekaterinburg
- C++设计模式实例图解
- 线性代数笔记:Hadamard积
- C语言关于signal()函数
- 开放搜索查询分析服务架构解读
- FilterDispatcher is deprecated!
- Android之 AudioTrack学习
- python os读取文件名_Python3基础 os.path.splitext 处理文件名,得到文件名+扩展名
- (pytorch-深度学习系列)CNN二维卷积层-学习笔记
- iOS开发笔记 2、Cocoa简明
- 「快学springboot」SpringBoot整合freeMark模板引擎
- BZOJ - 4568 幸运数字
- oracle数据库驱动下载(ojdbc)
- JavaScript 事件
- 照明中的微波感应是什么?
- 粘结剂菱镁板建筑材料英国UKCA认证—EN 14016-1
- yolov3代码详解(七)
- netlist compile速记
- 读书笔记: 变系数波方程