1、avb校验库的主入口
avb校验功能主要是由external/avb/libavb库实现的,该库主要完成的工作包括各个分区镜像的校验,签名验证,以及vbmeta数据的解析,包括了各种flags的处理以及dm-verity所需要的参数解析。avb校验库的主入口为:
avb_slot_verify(AvbOps* ops, const char* const* requested_partitions, const char* ab_suffix, AvbSlotVerifyFlags flags, AvbHashtreeErrorMode hashtree_error_mode, AvbSlotVerifyData** out_data)
以高通平台为例,avb校验在一次启动过程中总共进行了两次,第一次是在bootloader中进行校验,通过上面的接口校验各个分区的根hash和签名。第二次是在上层init中进行的,这次校验也是调用相同的接口,可能个别传入的参数会有不同。(这里再前面的宏观篇讲的很清楚)
之所以在init中再做一次的原因是什么呢?
因为我们挂载分区和dm-verity相关的参数都在镜像的vbmeta结构中保存,因此可以使用该接口进行解析和挂载,但是该接口和校验功能是捆绑的,所以必须要再进行一次校验。
不管是在bootloader还是在上层init中,所有校验都是从vbmeta分区开始,然后读取其中包含的各个分区的信息,然后依次循环读取并校验其他的各个分区,所有vbmeta.img中包含的分区都必须校验通过才认为是校验成功。(有个链式的逻辑)
1、bootloader
首先上代码,看到代码别慌,前辈博客有说明,没有的话,我也有。
CONST CHAR8 *RequestedPartitionMission[] = {"boot", "dtbo", NULL}; CONST CHAR8 *RequestedPartitionRecovery[] = {"recovery", "dtbo", NULL}; if ((!Info->MultiSlotBoot) && Info->BootIntoRecovery) { RequestedPartition = RequestedPartitionRecovery; NumRequestedPartition = ARRAY_SIZE (RequestedPartitionRecovery) - 1; if (Info->NumLoadedImages) { /* fastboot boot option, skip Index 0, as boot image already * loaded */ RequestedPartition = &RequestedPartitionRecovery[1]; } } else { RequestedPartition = RequestedPartitionMission; NumRequestedPartition = ARRAY_SIZE (RequestedPartitionMission) - 1; if (Info->NumLoadedImages) { /* fastboot boot option, skip Index 0, as boot image already * loaded */ RequestedPartition = &RequestedPartitionMission[1]; } } if (Info->NumLoadedImages) { NumRequestedPartition--; } if (FixedPcdGetBool (AllowEio)) { VerityFlags = IsEnforcing () ? AVB_HASHTREE_ERROR_MODE_RESTART : AVB_HASHTREE_ERROR_MODE_EIO; } else { VerityFlags = AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE; } Result = avb_slot_verify (Ops, RequestedPartition, SlotSuffix, VerifyFlags, VerityFlags, &SlotData);
avb校验是所有分区都要进行校验的,这里传入的RequestedPartition并不代表要校验的分区,进一步跟进可以发现最终和RequestedPartition只会对Hash descriptor的解析有影响,其他类型的descriptor解析方法都是一样的。
高通平台的vbmeta.img包含如下信息:
Minimum libavb version: 1.0 Header Block: 256 bytes Authentication Block: 576 bytes Auxiliary Block: 3456 bytes Algorithm: SHA256_RSA4096 Rollback Index: 0 Flags: 0 Release String: 'avbtool 1.1.0' Descriptors: Chain Partition descriptor: Partition Name: system Rollback Index Location: 2 Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d Chain Partition descriptor: Partition Name: recovery Rollback Index Location: 1 Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011 Hash descriptor: Image Size: 35553280 bytes Hash Algorithm: sha256 Partition Name: boot Salt: baa1ce5d7db69d1b3943a78b5b142ae4d77b4ed60b9885c8661e845172b29a13 Digest: ec7cb1ad89fed3104a03191434d5487b5b4acc78e6bfeb84d7178af40df7db75 Flags: 0 Hashtree descriptor: Version of dm-verity: 1 Image Size: 1056714752 bytes Tree Offset: 1056714752 Tree Size: 8327168 bytes Data Block Size: 4096 bytes Hash Block Size: 4096 bytes FEC num roots: 2 FEC offset: 1065041920 FEC size: 8421376 bytes Hash Algorithm: sha1 Partition Name: vendor Salt: abbf0829ed7bc08913b83f9a994a37ad2a85b5e9 Root Digest: 39a22a035ebff2d339dc682603adedb91da01374 Flags: 0 Hash descriptor: Image Size: 176641 bytes Hash Algorithm: sha256 Partition Name: dtbo Salt: 386837807aa5a7d9cbe51e7f768009f4e5fca5190af4b3e856a7c96a96c33e0a Digest: dabdbe5be19c38a3428efd046182d215f8522ab7cd3804e84f196fe73e9052f7 Flags: 0
因此受到影响的只有boot/dtbo/recovery,recovery这里是Chain Partition descriptor,但是解析recovery.img发现它校验数据也是生成的Hash,而不是Hashtree的方式。
RequestedPartition代表的是需要被校验的hash分区,只有其中包含的Hash分区,那么才会被校验。因此根据boot mode方式的不同,需要校验的hash分区也不同,正常启动需要校验:boot/dtbo,recovery启动需要校验:recovery/dtbo,而其他分区的校验方式是一样的。
2、init
针对init中的校验,由于我们已经boot成功到上层了,虽然该函数依然会轮询vbmeta包含的各个分区,但是此时kernel和dtbo都已经加载完毕了,没有必要在对Hash分区进一步进行校验了,所以我们可以传入RequestedPartition为空指针,这样init当检测到是Hash descriptor会直接return OK,并继续下一个分区的校验。
init中和AVB相关的主要是进行校验和挂载分区的功能,终极目的还是为了挂载,可以分开来看,有firststagemount和secondstagemount两种方式:(AVB源码系列有讲这个)
第一种方式是为了挂载vendor分区,需要通过libavb对system/vendor进行校验,system分区已经由kernel挂载过了,因此这里只需要利用dm-verity参数挂载vendor分区,因为vendor中包含了很多init rc文件,所以vendor必须要在这些rc执行之前被挂载上,所以必须要在第一阶段把vendor分区挂载起来,然后在init的第二阶段去执行init rc。
第二种方式是其他分区的挂载,主要是通过读取fstab中的挂载项进行挂载,这就和之前版本的android挂载方式一样了。
需要注意的是,由于第一阶段的挂载,我们没有办法通过fstab文件传递给init,因为fstab在vendor中,而vendor还没有被挂载起来,所以我们可以通过device tree的方式来传递fstab参数。这样init在第一阶段通过读取device tree来获取要在第一阶段挂载的分区,也就是我们的vendor分区,由于avb和dm-verity是绑定的,所以还需要传入对应的vbmeta节点,目的是开启dm-verity功能,同时也会对vbmeta.img中存在的各个分区进行校验。
当然我们校验是传入RequestedPartition为空指针,以跳过Hash分区的校验,只进行其他分区的校验。
这里需要特别注意的是device tree中的vbmeta节点的配置,必须要和vbmeta.img中所包含的分区一致,因为这个匹配校验也会做,否则会报校验失败,从而无法启动android。
firmware: firmware { android { compatible = "android,firmware"; vbmeta { compatible = "android,vbmeta"; parts = "vbmeta,boot,system,vendor,dtbo,recovery"; }; fstab { compatible = "android,fstab"; vendor { compatible = "android,vendor"; dev = "/dev/block/platform/soc/8804000.sdhci/by-name/vendor"; type = "ext4"; mnt_flags = "ro,barrier=1,discard"; fsmgr_flags = "wait,avb"; status = "ok"; }; }; }; };
这个vbmeta中包含"vbmeta,boot,system,vendor,dtbo,recovery"这几个分区,恰好和上面解析出来的一直,否则会报如下错误:
[ 11.254728] init: init first stage started! [ 11.260572] init: Using Android DT directory /proc/device-tree/firmware/android/ [ 11.274173] init: [libfs_mgr]fs_mgr_read_fstab_default(): failed to find device default fstab [ 11.463441] init: [libfs_mgr]by-name symlink not found for partition: 'recovery' [ 11.471083] init: [libfs_mgr]avb_slot_verify failed, result: 2
这是一个去掉recovery分区的一个报错实例。
init调用libavb库的主入口函数如下:
AvbSlotVerifyResult FsManagerAvbOps::AvbSlotVerify(const std::string& ab_suffix, AvbSlotVerifyFlags flags, AvbSlotVerifyData** out_data) { // Invokes avb_slot_verify() to load and verify all vbmeta images. // Sets requested_partitions to nullptr as it's to copy the contents // of HASH partitions into handle>avb_slot_data_, which is not required as // fs_mgr only deals with HASHTREE partitions. const char* requested_partitions[] = {nullptr}; // The |hashtree_error_mode| field doesn't matter as it only // influences the generated kernel cmdline parameters. return avb_slot_verify(&avb_ops_, requested_partitions, ab_suffix.c_str(), flags, AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, out_data); }
可以看出,其传入的requested_partitions确实是为空nullptr,这样就会跳过Hash分区校验。
解析vbmeta数据中的Hash descriptor:
switch (desc.tag) { case AVB_DESCRIPTOR_TAG_HASH: { AvbSlotVerifyResult sub_ret; sub_ret = load_and_verify_hash_partition(ops, requested_partitions, ab_suffix, allow_verification_error, descriptors[n], slot_data); if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) { ret = sub_ret; if (!allow_verification_error || !result_should_continue(ret)) { goto out; } } } break;
load_and_verify_hash_partition中判断requested_partitions是否存在对应的分区名,不存在则返回OK跳转:
/* Don't bother loading or validating unless the partition was * requested in the first place. */ found = avb_strv_find_str(requested_partitions, (const char*)desc_partition_name, hash_desc.partition_name_len); if (found == NULL) { ret = AVB_SLOT_VERIFY_RESULT_OK; goto out; }
load_and_verify_hash_partition中判断requested_partitions是否存在对应的分区名,不存在则返回OK跳转:
/* Don't bother loading or validating unless the partition was * requested in the first place. */ found = avb_strv_find_str(requested_partitions, (const char*)desc_partition_name, hash_desc.partition_name_len); if (found == NULL) { ret = AVB_SLOT_VERIFY_RESULT_OK; goto out; }
2、禁止verification校验功能
通过我的另一篇文章《Android P 如何挂载system镜像到根目录》的介绍,大家应该会有所了解,vbmeta.img中保存有一个flags:
typedef enum { AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = (1 << 0), AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED = (1 << 1) } AvbVBMetaImageFlags;
该flags会有两个bit,分别代表是否使能dm-verity和verification功能,本文介绍的AVB校验功能就是verification使能的时候才会进行。
我们可以通过修改此bit位来禁止AVB校验功能,利用fastboot重新刷写vbmeta.img:
fastboot --disable-verification flash vbmeta vbmeta.img
跳过AVB的代码如下所示,在函数avb_slot_verify中:
if (toplevel_vbmeta.flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED) { /* Since verification is disabled we didn't process any * descriptors and thus there's no cmdline... so set root= such * that the system partition is mounted. */ avb_assert(slot_data->cmdline == NULL); slot_data->cmdline = avb_strdup("root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)"); if (slot_data->cmdline == NULL) { ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto fail; } } else { ...... //verification operation }
这里发现禁止校验之后,函数就结束了,校验操作在else中进行的。
3、禁止dm-verity功能
两种方法:
adb disable-verity
fastboot --disable-verity flash vbmeta vbmeta.img
最终都是更新了vbmeta分区中保存的对应的flags来确定是否禁止dm-verity的。