增加 Android P 中的 AVB 校验.

Signed-off-by: ithink.chan <chenyang@autoai.com>
This commit is contained in:
ithink.chan 2020-02-10 10:10:52 +08:00
parent 9f24766872
commit 10d3b85f32
1 changed files with 330 additions and 0 deletions

View File

@ -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/recoveryrecovery 这里是 Chain Partition descriptor但是解析 recovery.img 发现它校验数据也是生成的 Hash而不是 Hashtree 的方式。 RequestedPartition 代表的是需要被校验的 hash 分区,只有其中包含的 Hash 分区,那么才会被校验。因此根据 boot mode 方式的不同,需要校验的 hash 分区也不同正常启动需要校验boot/dtborecovery 启动需要校验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 的。