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中包含的分区都必须校验通过才认为是校验成功。
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.0Header Block: 256 bytesAuthentication Block: 576 bytesAuxiliary Block: 3456 bytesAlgorithm: SHA256_RSA4096Rollback Index: 0Flags: 0Release String: 'avbtool 1.1.0'Descriptors:Chain Partition descriptor:Partition Name: systemRollback Index Location: 2Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617dChain Partition descriptor:Partition Name: recoveryRollback Index Location: 1Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011Hash descriptor:Image Size: 35553280 bytesHash Algorithm: sha256Partition Name: bootSalt: baa1ce5d7db69d1b3943a78b5b142ae4d77b4ed60b9885c8661e845172b29a13Digest: ec7cb1ad89fed3104a03191434d5487b5b4acc78e6bfeb84d7178af40df7db75Flags: 0Hashtree descriptor:Version of dm-verity: 1Image Size: 1056714752 bytesTree Offset: 1056714752Tree Size: 8327168 bytesData Block Size: 4096 bytesHash Block Size: 4096 bytesFEC num roots: 2FEC offset: 1065041920FEC size: 8421376 bytesHash Algorithm: sha1Partition Name: vendorSalt: abbf0829ed7bc08913b83f9a994a37ad2a85b5e9Root Digest: 39a22a035ebff2d339dc682603adedb91da01374Flags: 0Hash descriptor:Image Size: 176641 bytesHash Algorithm: sha256Partition Name: dtboSalt: 386837807aa5a7d9cbe51e7f768009f4e5fca5190af4b3e856a7c96a96c33e0aDigest: dabdbe5be19c38a3428efd046182d215f8522ab7cd3804e84f196fe73e9052f7Flags: 0
因此受到影响的只有boot/dtbo/recovery,recovery这里是Chain Partition descriptor,但是解析recovery.img发现它校验数据也是生成的Hash,而不是Hashtree的方式。 RequestedPartition代表的是需要被校验的hash分区,只有其中包含的Hash分区,那么才会被校验。因此根据boot mode方式的不同,需要校验的hash分区也不同,正常启动需要校验:boot/dtbo,recovery启动需要校验:recovery/dtbo,而其他分区的校验方式是一样的。
init
针对init中的校验,由于我们已经boot成功到上层了,虽然该函数依然会轮询vbmeta包含的各个分区,但是此时kernel和dtbo都已经加载完毕了,没有必要在对Hash分区进一步进行校验了,所以我们可以传入RequestedPartition为空指针,这样init当检测到是Hash descriptor会直接return OK,并继续下一个分区的校验。
init中和AVB相关的主要是进行校验和挂载分区的功能,终极目的还是为了挂载,可以分开来看,有firststagemount和secondstagemount两种方式,第一种方式是为了挂载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;}
禁止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中进行的。
禁止dm-verity功能
两种方法:
adb disable-verity
fastboot --disable-verity flash vbmeta vbmeta.img
最终都是更新了vbmeta分区中保存的对应的flags来确定是否禁止dm-verity的。