前言
Android O/P 版本以来,谷歌加入了A/B system的特性,此时ramdisk和system是一起放在同一个system.img镜像中的。
而系统起来之后也就不存在system分区了,而是直接把system镜像挂载到/根目录上。那么这个操作是怎么进行的呢?
system.img默认是需要使能dm-verity来挂载的,那么这就涉及到如何使能dm-verity来挂载/根分区。还有一个重要的点,如果我们想要禁用dm-verity功能又要如何操作?这又涉及更深的层次,如果配置/根分区的挂载方式为dm-verity或者非dm-verity方式?带着这些问题,我们进行逐步的深入研究。
1、vbmeta
Android P 中有一个vbmeta分区,这是一个主要起检验作用的分区,存放有各个分区的校验数据以及签名和其他信息。主要分三个部分:Vbmeta Header(头部)/Authentication Data(校验数据)/Auxiliary Data(其他附加信息)。我们可以使用android在带的avbtool对vbmeta.img进行解析:
avbtool info_image --image vbmeta.img > vbmeta.img.info
解析出来的结果实例如下:
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
1、Vbmeta Header
在Android自带的Library/avb/libavb/avb_vbmeta_image.h中可以找到该结构体的定义:
typedef struct AvbVBMetaImageHeader { ... /* 120: Flags from the AvbVBMetaImageFlags enumeration. This must be * set to zero if the vbmeta image is not a top-level image. */ uint32_t flags; ... }AVB_ATTR_PACKED AvbVBMetaImageHeader;
这里我们仅仅截取关键的部分 flags,这个成员标志着是否要使能dm-verity或者verification。
typedef enum { AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = (1 << 0), AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED = (1 << 1) } AvbVBMetaImageFlags;
这个也就是今天要解说的重点了,刚刚我们提到system image使用dm-verity挂载应该是一个可配置选项,那这个可配置功能就是利用这个flag中的AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED位。如果我们利用adb disable-verity命令操作了设备,设备就会去重写vbmeta分区中对应的该bit位为1,这样在后续的bootloader加载时就会做这个flag的判断,由此确定是否使能dm-verity去挂载system镜像。
2、Authentication Data
校验数据主要包含了hash摘要和签名,这是为了AVB启动准备的,不再本文讨论范围内,不做深入讲解。
3、Auxiliary Data
附加数据在system挂载时是需要讲解的,附加数据包含了三类信息:描述符/公钥/公钥元数据。
主要介绍描述符,上面的实例可以看到,在Descriptors:一项的后面有很多内容,描述符包括很多类型的描述符,其中有一个与system挂载有关,那就是cmdline descriptor,当然这个并不在vbmeta.img中有体现,因为vbmeta并不需要挂载,这个描述符只会存在于需要被kernel挂载的分区中。
cmdline
cmdline是uboot引导内核启动时传递给内核的,作用是指导内核启动。内核启动阶段会去解析cmdline,并根据cmdline去指导内核启动。 (1)格式就是由很多个项目用空格隔开依次排列,每个项目中都是项目名=项目值; (2)整个cmdline会被内核启动时解析,解析成一个一个的项目名=项目值的字符串。这些字符串又会被再次解析从而影响启动过程。
这里需要提到一些背景,我们知道system.img现在已经和ramdisk打包在一起了,那么system.img就不是挂载到/system的地方了,而是需要直接挂载在/根目录上。
以往的版本,对于system的挂载都是由init来完成的,我们只需要修改fstab即可完成对system的挂载,但是现在不同了,根目录的挂载必须要由kernel去完成了。
因为根目录挂载是先于init的运行的,没有根目录就不会有init。那么如何由kernel去挂载system.img镜像呢?
首先我们解析出来system.img中的vbmeta校验数据:
avbtool info_image --image system.img > system.img.info
解析出来的结果实例如下:
Footer version: 1.0 Image size: 3221225472 bytes Original image size: 3170316288 bytes VBMeta offset: 3220549632 VBMeta size: 1856 bytes -- Minimum libavb version: 1.0 (Sparse) Header Block: 256 bytes Authentication Block: 320 bytes Auxiliary Block: 1280 bytes Algorithm: SHA256_RSA2048 Rollback Index: 0 Flags: 0 Release String: 'avbtool 1.1.0' Descriptors: Hashtree descriptor: Version of dm-verity: 1 Image Size: 3170316288 bytes Tree Offset: 3170316288 Tree Size: 24969216 bytes Data Block Size: 4096 bytes Hash Block Size: 4096 bytes FEC num roots: 2 FEC offset: 3195285504 FEC size: 25264128 bytes Hash Algorithm: sha1 Partition Name: system Salt: 1215bb10e3488f3f030d9f412c29dd5f3ca07d5a Root Digest: ac8d587b82748d9128e84e8cfd2c004889ba3fd4 Flags: 0 Kernel Cmdline descriptor: Flags: 1 Kernel Cmdline: 'dm="1 vroot none ro 1,0 6192024 verity 1 PARTUUID=$(ANDROID_SYSTEM_PARTUUID) PARTUUID=$(ANDROID_SYSTEM_PARTUUID) 4096 4096 774003 774003 sha1 ac8d587b82748d9128e84e8cfd2c0 04889ba3fd4 1215bb10e3488f3f030d9f412c29dd5f3ca07d5a 10 $(ANDROID_VERITY_MODE) ignore_zero_blocks use_fec_from_device PARTUUID=$(ANDROID_SYSTEM_PARTUUID) fec_roots 2 fec_blocks 780099 fec_start 780099" root=/dev/dm-0' Kernel Cmdline descriptor: Flags: 2 Kernel Cmdline: 'root=PARTUUID=$(ANDROID_SYSTEM_PARTUUID)'
这里我们终于可以看到前文提到的Kernel Cmdline descriptor,但是很奇怪,这里为什么会有两个描述符呢?
还记得前面说的可配置功能吗?
- 这里第一个cmdline描述符,是使能dm-verity要使用的cmdline,
- 而第二个cmdline描述符,则是禁止掉dm-verity要使用的cmdline。
bootloader中需要根据我们前面vbmeta分区中的flag值来确定使用哪一个描述符。然后把cmdline添加到最终的cmdline中并写回到dtb(内存中的dtb),然后把dtb在ram中的存放地址传递给kernel去启动。
typedef enum { AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED = (1 << 0), AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED = (1 << 1) } AvbKernelCmdlineFlags;
/* Compare the flags for top-level VBMeta struct with flags in * the command-line descriptor so command-line snippets only * intended for a certain mode (dm-verity enabled/disabled) * are skipped if applicable. */ apply_cmdline = true; if (toplevel_vbmeta_flags & AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED) { if (kernel_cmdline_desc.flags & AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_NOT_DISABLED) { apply_cmdline = false; } } else { if (kernel_cmdline_desc.flags & AVB_KERNEL_CMDLINE_FLAGS_USE_ONLY_IF_HASHTREE_DISABLED) { apply_cmdline = false; } }
上面是轮询描述符的操作,其中的toplevel_vbmeta_flags就是vbmeta.img中header保存的flags,如果其中AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED被设置为1,那么代表了dm-verity是禁止的,那么会选择cmdline descriptor中的flags为2的作为cmdline,反之选择cmdline descriptor中的flags为2的作为cmdline。