From 10d3b85f3219e2ee553259932e887a907e8d16ab Mon Sep 17 00:00:00 2001 From: "ithink.chan" Date: Mon, 10 Feb 2020 10:10:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=20Android=20P=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=20AVB=20=E6=A0=A1=E9=AA=8C.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: ithink.chan --- .../Android/AVB/Android_P_中的_AVB_校验.md | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 Software/Development/OperatingSystem/Android/AVB/Android_P_中的_AVB_校验.md diff --git a/Software/Development/OperatingSystem/Android/AVB/Android_P_中的_AVB_校验.md b/Software/Development/OperatingSystem/Android/AVB/Android_P_中的_AVB_校验.md new file mode 100644 index 0000000..3a89a1a --- /dev/null +++ b/Software/Development/OperatingSystem/Android/AVB/Android_P_中的_AVB_校验.md @@ -0,0 +1,330 @@ +# Android P 中的 AVB 校验 + +avb 校验功能主要是由 external/avb/libavb 库实现的,该库主要完成的工作包括各个分区镜像的校验,签名验证,以及 vbmeta 数据的解析,包括了各种 flags 的处理以及 dm-verity 所需要的参数解析。avb 校验库的主入口为 + +```cpp +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 + +```cpp +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 包含如下信息: + +```sh +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,而其他分区的校验方式是一样的。 + +## 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。 + +```sh +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,recover”这几个分区,恰好和上面解析出来的一致,否则会报如下错误: + +```sh +[ 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 库的主入口函数如下: + +```sh +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: + +```cpp +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 跳转: + +```cpp +/* 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: + +```cpp +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: + +```sh +fastboot --disable-verification flash vbmeta vbmeta.img +``` + +跳过 AVB 的代码如下所示,在函数 avb_slot_verify 中: + +```cpp +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 功能 + +两种方法: + +```sh +adb disable-verity +# 或者 +fastboot --disable-verity flash vbmeta vbmeta.img +``` + +最终都是更新了 vbmeta 分区中保存的对应的 flags 来确定是否禁止 dm-verity 的。