增加 Android P 中的 AVB 校验.
Signed-off-by: ithink.chan <chenyang@autoai.com>
This commit is contained in:
parent
9f24766872
commit
10d3b85f32
|
@ -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 的。
|
Loading…
Reference in New Issue