Android P中的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中包含的分区都必须校验通过才认为是校验成功。

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,而其他分区的校验方式是一样的。

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的。

你可能感兴趣的:(android)