Android Verified Boot 2.0 安卓P AVB实现详解

什么是AVB

验证启动是确保用户设备运行软件完整性的一整套流程。 它通常从设备固件的只读部分启动,使用加密方式验证代码是可靠且没有任何已知的安全缺陷之后才会执行。 AVB是验证启动的一种实现。

VBMeta结构体

AVB中使用的核心数据结构是VBMeta结构体。此数据结构包含许多描述符(和其他元数据),并且所有这些数据都以加密方式签名。 描述符用于映像哈希值,映像哈希树元数据和所谓的链接分区。 下面是一个简单的例子:

image

其中vbmeta分区在哈希描述符中保存引导分区的哈希值。对于systemvender分区,哈希树紧随在各自的分区文件系统数据之后,而vbmeta分区只保存哈希树描述符中哈希树的根哈希(root hash),盐(salt)和偏移量(offset)。因为vbmeta分区中的VBMeta结构体是加密签名的,所以引导加载程序可以检查签名并验证它是否由key0的所有者制作(例如通过已嵌入的key0公钥),从而信任分区中储存的bootsystemvendor分区的哈希值。

链接分区描述符用于委派权限,它包含委派权限的分区的名称以及该特定分区上已经签名的可信公钥。 例如,请考虑下面的设置:

image

在这个设置中,xyz分区包含用于完整性校验的哈希树。在哈希树之后是一个VBMeta结构体,它包含带有哈希树元数据的哈希树描述符(根哈希,盐和偏移量),这个结构使用key1签名。最后,在分区的末尾是一个有VBMeta结构体偏移量的页脚。

这个设置允许引导加载程序使用链接分区描述符在分区末尾找到页脚(使用链接分区描述符中的名称),这有助于定位VBMeta结构体并验证它是否由key1签名(使用存储在链接分区描述符中的key1_pub)。至关重要的是,因为有一个带有偏移量的页脚,所以可以更新xyz分区,而不需要vbmeta分区进行任何更改(译者注:使用key1公钥验证更新后的xyz分区签名即可)。

VBMeta结构体非常灵活,任何分区的哈希描述符和哈希树描述符都可以储存在vbmeta分区或者其它用于完整性校验的分区(通过使用链接分区描述符)以及其它任意分区。这样主要是考虑到大范围的组织化可信关系。

在将链接分区指向VBMeta结构体位于开头的分区(就像vbmeta分区一样)时,该分区可以不使用页脚。这对于整个组织拥有的分区的哈希和哈希树描述符都存储在专用分区(例如vbmeta_google)中的用例非常有用。在此示例中,系统的哈希树描述符位于vbmeta_google分区中,这意味着引导加载程序根本不需要访问system分区,这对于将system分区作为逻辑分区进行管理的实例是非常有用的(例如LVM或类似技术)。

回滚保护

AVB还包括回滚保护,用于防范已知的安全漏洞。每个VBMeta结构体都有一个回滚索引(rollback index),如下所示:

image

这些数字使用rollback_index[n]表示,任意映像发现并修复安全漏洞后,这个数字都会增加。 此外,设备会将最后看到的回滚索引存储在防篡改存储中:

image

这些被称为stored_rollback_index[n]

在使用回滚保护的设备上,只有当所有n都满足rollback_index[n] >= stored_rollback_index[n]这个条件时才可以加载映像,并且设备会随着时间增加stored_rollback_index[n]。 具体如何完成此操作将在“更新存储的回滚索引”部分中进行讨论。

A/B 分区支持

AVB被设计成可以与A/B分区同时启用,它要求存储在描述符中的任何分区名称中都不使用A/B后缀。这是一个有两个插槽的例子:

image

注意不同分区的回滚索引是不一样的——对于槽A,回滚索引是[42,101],对于槽B,它们是[43,103]

在1.1或更高版本中,avbtool的add_hash_footeradd_hashtree_footer操作增加了可选参数--do_not_use_ab。这样那些没有A/B分区且也不应该有前缀的分区就可以使用AVB了。这对应于AVB_HASH[TREE]_DESCRIPTOR_FLAGS_DO_NOT_USE_AB标志。

VBMeta摘要

VBMeta摘要是所有VBMeta结构体的摘要,包括根结构体(例如在vbmeta分区中)和链接分区中的所有VBMeta结构体。 可以使用avbtool calculate_vbmeta_digest在构建时计算此摘要,也可以在运行时使用avb_slot_verify_data_calculate_vbmeta_digest()函数计算此摘要。在内核命令行中,它被设置为androidboot.vbmeta.digest。有关详细信息,请参阅avb_slot_verify()文档。

此摘要可以与加载的操作系统用户空间中的libavb一起使用,以验证加载的vbmeta结构体的真实性。如果信任根或存储的回滚索引仅在引导加载程序中运行时可用,则此选项非常有用。

此外,如果硬件级的可信数据中包含VBMeta摘要,则依赖方可以提取摘要并将其与已知完好操作系统的摘要列表进行比较,如果找到,则可以为运行应用程序的设备提供额外保证。

工具和库

本节主要内容为AVB中的工具和库的相关信息。

avbtool和libavb

avbtool主要用来生成vbmeta.img,它是验证启动的顶级对象。这个映像将被烧录到vbmeta分区(如果使用A/B分区,则为vbmeta_avbmeta_b)而且被设计的尽可能的小(用于带外更新out-of-band update)。vbmeta映像使用密钥签名,映像中包含用于验证boot.imgsystem.img和其他分区映像的验证数据(例如加密摘要)。

vbmeta映像还可以包含对存储验证数据的其他分区的引用,以及指定验证数据使用的公钥。这种间接方式可以将验证权进行委托,它允许第三方通过在vbmeta.img中包含它们的公钥来控制给定分区上的内容。这样只需通过更新vbmeta.img中的分区描述符,就可以轻松改变或撤销验证权限而不用修改其它分区。

将签名的验证数据存储在其他映像上(例如boot.imgsystem.img)也是使用avbtool完成的。

除了avbtool之外,还提供了一个库——libavb。该库在设备端执行所有验证,例如它首先加载vbmeta分区,检查签名,然后继续加载启动分区以进行验证。此库旨在用于引导加载程序和Android内部。它有一个简单的系统依赖抽象(参见avb_sysdeps.h)以及引导加载程序或操作系统应该实现的操作(参见avb_ops.h)。验证的主要入口点是avb_slot_verify()

Adroid Things(译者注:一套谷歌推出的面向物联网平台的操作系统)对vbmeta公钥有特定的要求和验证逻辑。libavb_atx中提供了一个扩展,该扩展是libavb公钥验证操作的实现。(请参阅avb_ops.h中的avb_validate_vbmeta_public_key())。

文件和目录
  • libavb/
    • 映像验证的实现。这段代码可移植行强,它可以在尽可能多的上下文中使用,但编译器需要支持C99标准。库中的部分代码是算法的内部实现,应避免在外部使用。例如avb_rsa.[ch]avb_sha.[ch]文件。平台预期提供的系统依赖关系在avb_sysdeps.h中定义。如果平台提供标准C运行环境,则可以使用avb_sysdeps_posix.c
  • libavb_atx/
    • 用于验证公钥元数据的Android Things扩展。
  • libavb_user/
    • 包含适用于Android用户空间的AvbOps实现。用于boot_control.avbavbctl
  • libavb_ab/
    • 用于引导加载程序和AVB示例的实验性A/B实现。注意:此代码是DEPRECATED,您必须定义AVB_AB_I_UNDERSTAND_LIBAVB_AB_IS_DEPRECATED才能使用它。该代码已经在2018年6月1日删除。
  • boot_control/
    • Android boot_control HAL的一个实现,用于使用实验性libavb_abA/B堆栈的引导加载程序。注意:此代码已弃用,已经在2018年6月1日删除。
  • contrib/
    • 包含其他项目中与AVB交互的补丁。例如,contrib/linux/4.4有Linux内核4.4的补丁,它们是由git format-patch生成的。
  • Android.bp
    • 构建libavb(用于设备上的静态库)、宿主端库(用于单元测试)和单元测试的构建说明。
  • avbtool
    • 用Python编写的工具,用于处理与验证启动相关的映像。
  • test/
    • abvtool, libavb, libavb_ab, 和libavb_atx的测试单元。
  • tools/avbctl/
    • 包含可用于在Android中运行时控制AVB的工具的源代码。
  • examples/uefi/
    • 包含使用libavb/libavb_ab/的基于UEFI引导加载程序的源代码。
  • examples/things/
    • 包含适用于Android Things的插槽验证的源代码。
  • README.md
    • README。
  • docs/
    • 说明文档。
可移植性(Portability)

libavb代码在加载Android或其他操作系统的设备引导加载程序中使用。建议的方法是将上一节中提到的相应头文件和C文件复制到引导加载程序中,并根据需要进行集成。

libavb/ 代码库会随着时间的推移不断更新优化,集成应尽可能无创。目的是保持库的API稳定,但必要时也会进行修改。至于可移植性,该库在设计时就以高度可移植为目标,适用于小端和大端架构以及32位和64位。它还可以在没有标准C库和运行环境的非标准环境中工作。

如果设置了AVB_ENABLE_DEBUG预处理器符号,则代码将包含有用的调试信息和运行检查。生产构建中不应该使用该符号。只应在编译库时设置预处理程序符号AVB_COMPILATION。代码必须编译成一个单独的库。

使用已编译的libavb库的应用程序只能包含libavb/libavb.h文件(将包括所有公共接口),并且必须没有avb_compile预处理器符号集。这是为了确保将来可能更改的内部代码(例如avb_sha.[ch]avb_rsa.[ch])对应用程序代码不可见。

版本控制和兼容性

AVB使用具有三个字段的版本号——主要版本,次要版本和子版本。 这是一个示例版本号:

                     1.4.3
                     ^ ^ ^
                     | | |
the major version ---+ | |
the minor version -----+ |
  the sub version -------+
Null
Copy

只有在兼容性被破坏时,主要版本号才会受到影响,例如结构体字段已被删除或更改。只有在引入新功能时才会触发次要版本号,例如添加了新算法或描述符。当修复错误或进行其他不影响兼容性的更改时,子版本号都会更改。

AvbVBMetaImageHeader结构(在avb_vbmeta_image.h中定义)包含验证相关结构体所需的主副本版本号libavb。它存储在required_libavb_version_majorrequired_libavb_version_minor字段中。此外,此结构体包含一个文本字段,其中包含用于创建结构体的avbtool版本,例如“avbtool 1.4.3”或“avbtool 1.4.3 some_board Git-4589fbec”。

请注意,AvbVBMetaImageHeader结构体可以包含如下信息:

required_libavb_version_major = 1
required_libavb_version_minor = 0
avbtool_release_string = "avbtool 1.4.3"
Shell
Copy
avbtool的使用

可以按如下方式生成vbmeta分区的内容:

$ avbtool make_vbmeta_image                                                    \
    [--output OUTPUT]                                                          \
    [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key]   \
    [--public_key_metadata /path/to/pkmd.bin] [--rollback_index NUMBER]        \
    [--include_descriptors_from_image /path/to/image.bin]                      \
    [--setup_rootfs_from_kernel /path/to/image.bin]                            \
    [--chain_partition part_name:rollback_index_location:/path/to/key1.bin]    \
    [--signing_helper /path/to/external/signer]                                \
    [--signing_helper_with_files /path/to/external/signer_with_files]          \
    [--print_required_libavb_version]                                          \
    [--append_to_release_string STR]
Shell
Copy

包含整个分区哈希的完整性页脚可以添加到现有映像,如下所示:

$ avbtool add_hash_footer                                                      \
    --partition_name PARTNAME --partition_size SIZE                            \
    [--image IMAGE]                                                            \
    [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key]   \
    [--public_key_metadata /path/to/pkmd.bin] [--rollback_index NUMBER]        \
    [--hash_algorithm HASH_ALG] [--salt HEX]                                   \
    [--include_descriptors_from_image /path/to/image.bin]                      \
    [--setup_rootfs_from_kernel /path/to/image.bin]                            \
    [--output_vbmeta_image OUTPUT_IMAGE] [--do_not_append_vbmeta_image]        \
    [--signing_helper /path/to/external/signer]                                \
    [--signing_helper_with_files /path/to/external/signer_with_files]          \
    [--print_required_libavb_version]                                          \
    [--append_to_release_string STR]                                           \
    [--calc_max_image_size]                                                    \
    [--do_not_use_ab]                                                          \
    [--use_persistent_digest]
Shell
Copy

可以将用于校验分区完整性的页脚添加到现有映像中,该页脚中包含根摘要和分区哈希树的盐。如下所示,哈希树也被添加到映像中。

$ avbtool add_hashtree_footer                                                  \
    --partition_name PARTNAME --partition_size SIZE                            \
    [--image IMAGE]                                                            \
    [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key]   \
    [--public_key_metadata /path/to/pkmd.bin] [--rollback_index NUMBER]        \
    [--hash_algorithm HASH_ALG] [--salt HEX] [--block_size SIZE]               \
    [--include_descriptors_from_image /path/to/image.bin]                      \
    [--setup_rootfs_from_kernel /path/to/image.bin]                            \
    [--setup_as_rootfs_from_kernel]                                            \
    [--output_vbmeta_image OUTPUT_IMAGE] [--do_not_append_vbmeta_image]        \
    [--do_not_generate_fec] [--fec_num_roots FEC_NUM_ROOTS]                    \
    [--signing_helper /path/to/external/signer]                                \
    [--signing_helper_with_files /path/to/external/signer_with_files]          \
    [--print_required_libavb_version]                                          \
    [--append_to_release_string STR]                                           \
    [--calc_max_image_size]                                                    \
    [--do_not_use_ab]                                                          \
    [--use_persistent_digest]
Shell
Copy

可以使用resize_image命令更改具有完整性页脚的映像的大小:

$ avbtool resize_image                                                         \
    --image IMAGE                                                              \
    --partition_size SIZE
Bash
Copy

可以从映像中删除映像上的完整性页脚。作为可选项,可以保留哈希树。

$ avbtool erase_footer --image IMAGE [--keep_hashtree]
Shell
Copy

对于哈希和哈希树映像,vbmeta结构体也可以通过--output_vbmeta_image选项写入外部文件,还可以指定不将vbmeta结构体和页脚添加到正在操作的映像中。

在使用avbtool add_hash_footeravbtool add_hashtree_footer命令之后,要计算适合已经指定大小的分区映像的最大值,请使用--calc_max_image_size选项:

$ avbtool add_hash_footer --partition_size $((10*1024*1024)) \
    --calc_max_image_size
10416128

$ avbtool add_hashtree_footer --partition_size $((1010241024))
–calc_max_image_size
10330112

Shell
Copy

在使用make_vbmeta_imageadd_hash_footeradd_hashtree_footer命令时,要计算需要放在vbmeta结构中的libavb版本,可以使用--print_required_libavb_version选项:

$ avbtool make_vbmeta_image \
    --algorithm SHA256_RSA2048 --key /path/to/key.pem \
    --include_descriptors_from_image /path/to/boot.img \
    --include_descriptors_from_image /path/to/system.img \
    --print_required_libavb_version
1.0
Shell
Copy

可以在make_vbmeta_imageadd_hash_footeradd_hashtree_footer命令中使用--signing_helper选项来指定用于签名哈希的外部程序。要签名的数据通过STDIN输入,签名的数据通过STDOUT返回。如果--signing_helper出现在命令行中,--key选项只需要包含公钥。签名助手的参数是算法和公钥。如果签名助手结束时返回的不是0,则意味着失败。
这是一个示例调用:

/path/to/my_signing_program SHA256_RSA2048 /path/to/publickey.pem
Shell
Copy

--signing_helper_with_files类似于--signing_helper,只是使用一个临时文件与helper通信,而不是与STDINSTDOUT通信。在签名助手将诊断结果输出到STDOUT而不是STDERR的情况下,这非常有用。
下面是一个示例调用:

/path/to/my_signing_program_with_files SHA256_RSA2048 \
  /path/to/publickey.pem /tmp/path/to/communication_file
Shell
Copy

其中最后一个位置参数是包含要签名数据的文件。助手应该在这个文件中写入签名。

可以使用ppend_vbmeta_image命令将整个vbmeta二进制文件追加到另一个映像的末尾。这对于不使用任何vbmeta分区的情况非常有用,例如:

$ cp boot.img boot-with-vbmeta-appended.img
$ avbtool append_vbmeta_image                       \
    --image boot-with-vbmeta-appended.img           \
    --partition_size SIZE_OF_BOOT_PARTITION         \
    --vbmeta_image vbmeta.img
$ fastboot flash boot boot-with-vbmeta-appended.img
Shell
Copy

verify_image命令可用于同时验证多个映像文件的内容。在对象映像调用时,执行以下检查:

  • 如果映像具有VBMeta结构体,则根据嵌入的公钥检查签名。如果映像看起来不像vbmeta.img,则查找页脚,如果页脚存在就使用页脚。
  • 如果传递了--key选项,则需要一个.pem文件,并检查VBMeta结构体中嵌入的公钥是否和给定的密钥相匹配。
  • VBMeta结构体中的所有描述符都按以下方式检查:
    • 对于哈希描述符,加载对应分区名称的映像文件,并且根据描述符中的摘要检查映像摘要是否一致。
    • 对于哈希树描述符,加载分区名对应的映像文件,计算哈希树,并将其根摘要与描述符中的映像文件进行比较。
    • 对于链接分区描述符,将其内容与需要通过--expected_chain_partition选项传入的内容进行比较。 此选项的格式类似于--chain_partition选项的格式。如果链接分区描述符没有--expected_chain_partition描述符,则检查失败。

这是一个设置示例,其中boot.imgsystem.img的摘要存储在使用my_key.pem签名的vbmeta.img中。它还检查分区foobar的链接分区是否使用回滚索引8,以及AVB格式的公钥是否与文件foobar_vendor_key.avbpubkey的公钥匹配:

$ avbtool verify_image \
     --image /path/to/vbmeta.img \
     --key my_key.pem \
     --expect_chained_partition foobar:8:foobar_vendor_key.avbpubkey

Verifying image /path/to/vbmeta.img using key at my_key.pem
vbmeta: Successfully verified SHA256_RSA4096 vbmeta struct in /path_to/vbmeta.img
boot: Successfully verified sha256 hash of /path/to/boot.img for image of 10543104 bytes
system: Successfully verified sha1 hashtree of /path/to/system.img for image of 1065213952 bytes
foobar: Successfully verified chain partition descriptor matches expected data

Shell
Copy

在此示例中,verify_image命令验证目录/path/to中的文件vbmeta.imgboot.imgsystem.img。给定映像的目录和文件扩展名(例如/path/to/vbmeta.img)与描述符中的分区名称一起使用,以计算保存哈希和哈希树映像的文件名。

verify_image命令还可用于检查自定义签名助手是否按预期工作。

calculate_vbmeta_digest命令可用于同时计算多个映像文件的vbmeta摘要。 结果是十六进制字符串,将打印在STDOUT或提供的路径上(使用--output选项)。

$ avbtool calculate_vbmeta_digest \
     --hash_algorithm sha256 \
     --image /path/to/vbmeta.img
a20fdd01a6638c55065fe08497186acde350d6797d59a55d70ffbcf41e95c2f5
Shell
Copy

在此示例中,calculate_vbmeta_digest命令加载vbmeta.img文件。如果此映像具有一个或多个链接分区描述符,则使用与verify_image命令相同的逻辑来加载这些文件(例如,它假定与给定映像具有相同的目录和文件扩展名)。一旦加载了所有vbmeta结构体,就会计算摘要(使用--hash_algorithm选项指定使用的哈希算法)并打印出来。

编译集成

在Android中,AVB通过BOARD_AVB_ENABLE变量开启。

BOARD_AVB_ENABLE := true
Shell
Copy

设置该选项后,编译安卓时就会为system.img附加哈希树并创建vbmeta.imgvbmeta.img映像中包含boot.imgsystem.img的哈希描述符,以及为system.img设置dm-verity的内核命令行参数。 如果构建系统设置为构建vendor.img / product.img / odm.img / product_services.img中的一个或多个,则每个构建系统的哈希树也将分别附加各自的映像中,并且他们的哈希树描述符将相应地包含在vbmeta.img中。

默认使用SHA256_RSA4096算法和external/avb/test/data目录中的测试密钥。算法和密钥分别由BOARD_AVB_ALGORITHMBOARD_AVB_KEY_PATH变量指定,比如下面的示例就指定使用4096-bit RSA key and SHA-512算法:

BOARD_AVB_ALGORITHM := SHA512_RSA4096
BOARD_AVB_KEY_PATH := /path/to/rsa_key_4096bits.pem
Shell
Copy

需要注意的是,公钥应该对引导加载程序可用,这样才能用来校验对应的分区。使用avbtool extract_public_key以预期格式提取密钥(以下为AVB_pk)。如果设备使用的是与AVB_pk不同的信任根,那么--public_key_metadata选项可用于嵌入一个二进制文件(以下为AVB_pkmd),该文件可用于派生AVB_pk。在验证插槽(slot)时,AVB_pkAVB_pkmd都传递给validate_vbmeta_public_key()操作。

可以将设备配置为创建额外的vbmeta分区作为链接分区,以便在不更改顶级vbmeta分区的情况下更新子分区。例如,下面的变量创建vbmeta_mainline.img,作为一个链接vbmeta映像,它包含system.imgproduct_services.img的哈希树描述符。vbmeta_mainline.img本身将由指定的密钥和算法签名。

BOARD_AVB_VBMETA_MAINLINE := system product_services
BOARD_AVB_VBMETA_MAINLINE_KEY_PATH := external/avb/test/data/testkey_rsa2048.pem
BOARD_AVB_VBMETA_MAINLINE_ALGORITHM := SHA256_RSA2048
BOARD_AVB_VBMETA_MAINLINE_ROLLBACK_INDEX_LOCATION := 1
Shell
Copy

请注意,system.imgproduct_services.img的哈希树描述符将仅包含在vbmeta_mainline.img中,但不包含在vbmeta.img中。通过上面的设置,分区system.imgproduct_services.imgvbmeta_mainline.img可以独立更新。

目前,构建系统支持构建vbmeta_mainline.img (BOARD_AVB_VBMETA_MAINLINE)和vbmeta_vendor.img(BOARD_AVB_VBMETA_VENDOR)这样的链接vbmeta映像。

为防止回滚攻击,应定期增加回滚索引。可以使用BOARD_AVB_ROLLBACK_INDEX变量设置回滚索引:

BOARD_AVB_ROLLBACK_INDEX := 5
Shell
Copy

如果未设置,则回滚索引默认为0。

变量BOARD_AVB_MAKE_VBMETA_IMAGE_ARGS可用于指定传递给avbtool make_vbmeta_image的其他选项。这里使用的典型选项包括--prop-- prop_from_file--chain_partition--public_key_metadata--signing_helper

使用avbtool add_hash_footer制作boot.img时,变量BOARD_AVB_BOOT_ADD_HASH_FOOTER_ARGS可用于指定其他选项。这里使用的典型选项包括--hash_algorithm--salt

使用avbtool add_hash_footer制作system.img时,变量BOARD_AVB_SYSTEM_ADD_HASHTREE_FOOTER_ARGS可用于指定其他选项。这里使用的典型选项包括--hash_algorithm--salt--block_size--do_not_generate_fec

使用avbtool add_hash_footer制作vendor.img时,变量BOARD_AVB_VENDOR_ADD_HASHTREE_FOOTER_ARGS可用于指定其他选项。这里使用的典型选项包括括--hash_algorithm--salt--block_size--do_not_generate_fec

使用avbtool add_hash_footer制作dtbo.img时,变量BOARD_AVB_DTBO_ADD_HASH_FOOTER_ARGS可用于指定其他选项。这里使用的典型选项包括--hash_algorithm--salt

旧版Android中构建Verified Boot的系统变量(例如PRODUCT_SUPPORTS_VERITY_FEC)未在AVB中使用。

点击此处找到A/B相关的构建系统变量。

设备集成

本节讨论将libavb与设备引导加载程序集成的建议和最佳实践。重要的是要强调这些只是建议,因此用词必须谨慎。

此外,本章还使用术语HLOS来指代高级操作系统(High Level Operating System)。这显然包括Android(包括但不限于手机形式),但也可能是其他操作系统。

系统依赖

libavb库的编写方式使得它可以移植到任何使用C99编译器的系统。它不需要标准的C库,但是引导加载程序必须实现libavb所需的一组简单的系统原语,如avb_malloc()avb_free()avb_print()

除了系统原语之外,libavb还通过AvbOps结构体作为接口与引导加载程序交互。这包括从分区读取和写入数据,读取和写入回滚索引,检查是否应接受用于签名的公钥,等等。

锁定和解锁模式(Locked and Unlocked mode)

无论安卓设备处于锁定(LOCKED)或解锁(UNLOCKED)状态,都支持AVB。

在AVB的上下文中,LOCKED状态意味着验证错误是致命的,而在UNLOCKED状态则不是。 如果设备是UNLOCKED,则在avb_slot_verify()的flags参数中传递AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR标志,包括下面错误在内的验证错误将为非致命错误:

  • AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED
  • AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION
  • AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX

如果设备处于LOCKED状态,请不要在avb_slot_verify()的flags参数中传递AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR标志,并且仅将AVB_SLOT_VERIFY_RESULT_OK视为非致命错误。

在Android上,可以通过fastboot接口来改变设备状态。例如使用fastboot flashing lock 转换到LOCKED状态,使用fastboot flashing unlock转换到UNLOCKED状态。

在断言用户的物理存在之后,设备必须仅允许状态转换(例如从LOCKEDUNLOCKEDUNLOCKEDLOCKED)。如果设备具有显示器和按钮,则通常通过显示对话框并要求用户使用物理按钮来完成确认或取消操作。

LOCKED转换到UNLOCKED状态时,必须清除包括userdata分区和任何NVRAM(译者注: Non-Volatile Random Access Memory 非易失性随机访问存储器)在内的所有用户数据。此外,stored_rollback_index[n]所在地址也将被擦除(所有元素必须设置为零)。当从UNLOCKED转换为LOCKED时,也会发生类似的操作(擦除userdata,NVRAM空间和stored_rollback_index[n]位置)。如果要求设备使用全盘加密,则UNLOCKEDLOCKED只需要较少的擦除。根据设备的外形和预期用途,删除任何数据前都应采取对应方式提示用户确认。

防篡改存储(Tamper-evident Storage)

在本文档中,防篡改意味着可以检测HLOS是否已经篡改了数据,例如可以检测出数据被重写。

已记录的回滚索引,用于验证的密钥,设备状态(LOCKEDUNLOCKED)以及已命名的持久值都应存放在防篡改存储中。如果检测到篡改,则相应的AvbOps操作应该失败,例如 返回AVB_IO_RESULT_ERROR_IO。验证密钥不能被篡改尤为重要,因为它们代表了信任根。

如果验签密钥可能发生改变,则它们必须只能由最终用户设置,例如,绝不能在最终用户之前在工厂或商店或任何中间点设置。此外,只有在设备处于UNLOCKED状态时才能设置或清除密钥。

命名持久值(Named Persistent Values)

AVB1.1引入了对命名持久值的支持,这些值必须是防篡改的,并允许AVB存储任意键值对。集成商可以将对这些值的支持限制为一组固定的已知名称,最大值大小 和/或 最大值数量。

持久摘要(Persistent Digests)

对分区使用持久性摘要意味着摘要(或在哈希树的情况下为根摘要)不存储在描述符中,而是存储在命名的持久值中。这允许AVB验证可能因设备而异的配置数据。当设备处于LOCKED状态时,一定不能修改持久摘要,除非摘要不存时进行初始化。

要指定描述符应使用持久性摘要,请对add_hash_footeradd_hashtree_footer avbtool操作使用--use_persistent_digest选项。然后,在验证描述符期间,AVB将在命名的持久值avb.persistent_digest.$(partition_name)中查找摘要,而不是在描述符本身中查找摘要。

对于使用持久摘要的哈希树描述符,可以使用$(AVB_FOO_ROOT_DIGEST)形式的令牌将摘要值替换为内核命令行描述符,其中“FOO”是大写分区名,在本例中是名为“FOO”的分区。令牌将被十六进制形式的摘要所替代。

默认情况下,当--use_persistent_digest选项与add_hash_footeradd_hashtree_footer一起使用时,avbtool将生成一个没有盐(salt)的描述符,而不是生成默认与摘要长度相等的随机盐。这是因为摘要值存储在不随时间改变的永久存储器中。另一种选择是使用--salt手动提供随机盐。但是在写入持久摘要值之后,该盐就需要在设备的生命周期内保持不变。

更新已存储回滚索引(Updating Stored Rollback Indexes)

为了使回滚保护工作,引导加载程序需要在将控制权转移到HLOS之前更新设备上的stored_rollback_indexes[n]数组。如果不使用A/B,这很简单——只需在引导之前将其更新到槽的AVB元数据中。在伪代码中是这样的:

// The |slot_data| parameter should be the AvbSlotVerifyData returned
// by avb_slot_verify() for the slot we're about to boot.
//
bool update_stored_rollback_indexes_for_slot(AvbOps* ops,
                                             AvbSlotVerifyData* slot_data) {
    for (int n = 0; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) {
        uint64_t rollback_index = slot_data->rollback_indexes[n];
        if (rollback_index > 0) {
            AvbIOResult io_ret;
            uint64_t current_stored_rollback_index;
        io_ret <span class="token operator">=</span> ops<span class="token operator">-&gt;</span><span class="token function">read_rollback_index</span><span class="token punctuation">(</span>ops<span class="token punctuation">,</span> n<span class="token punctuation">,</span> ¤t_stored_rollback_index<span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>io_ret <span class="token operator">!=</span> AVB_IO_RESULT_OK<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token keyword keyword-return">return</span> false<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>rollback_index <span class="token operator">&gt;</span> current_stored_rollback_index<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            io_ret <span class="token operator">=</span> ops<span class="token operator">-&gt;</span><span class="token function">write_rollback_index</span><span class="token punctuation">(</span>ops<span class="token punctuation">,</span> n<span class="token punctuation">,</span> rollback_index<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token keyword keyword-if">if</span> <span class="token punctuation">(</span>io_ret <span class="token operator">!=</span> AVB_IO_RESULT_OK<span class="token punctuation">)</span> <span class="token punctuation">{</span>
                <span class="token keyword keyword-return">return</span> false<span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
        <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword keyword-return">return</span> true<span class="token punctuation">;</span>

}

C
Copy

但是,如果使用A/B的话则必须更加小心,当更新不起作用时,仍然允许设备回退到旧插槽。

对于像Android这样的HLOS,如果发现更新的OS版本不起作用,则仅支持回滚,stored_rollback_index[n]应仅从A/B元数据中标记为SUCCESSFUL的插槽更新。伪代码如下,其中slot_is_marked_as_successful()来自正在使用的A/B堆栈:

if (is_slot_is_marked_as_successful(slot->ab_suffix)) {
    if (!update_stored_rollback_indexes_for_slot(ops, slot)) {
        // TODO: handle error.
    }
}
C
Copy

对于可以回滚到先前版本的HLOS,stored_rollback_index[n]应设置为所有可以正常启动的插槽允许的最大值。此方法在AVB的实验性(现在已被弃用)A/B堆栈libavb_ab中实现,请参阅avb_ab_flow()实现。请注意,这需要在每次启动时验证所有可启动插槽,这可能会影响启动时间。

推荐引导流程

使用AVB的设备的建议引导流程如下:

image

注意事项:

  • 设备应搜索所有A/B插槽,直到找到有效的操作系统进行引导。在LOCKED状态下被拒绝的插槽可能在UNLOCKED状态下不被拒绝(例如,当UNLOCKED可以使用任何密钥并且允许回滚索引失败时),因此用于选择插槽的算法会根据设备处于何种状态而变化。
  • 如果找不到有效的操作系统(即没有可引导的A/B插槽),设备无法引导,必须进入修复模式。这取决于设备。如果设备有一个屏幕,它必须将这个状态传递给用户。
  • 如果设备被锁定,则只接受由已经固化的验证密钥签名的操作系统(见上一节)。此外,存储在已验证的映像中的rollback_index[n]必须大于或等于设备上的stored_rollback_index[n]中的值(对于所有的n),并且stored_rollback_index[n]数组应该按照上一节中指定的方式进行更新。
    • 如果用于验证的密钥是由最终用户设置的,并且设备有一个屏幕,那么它必须显示一个带有密钥指纹的警告,以表明设备正在启动一个定制操作系统。在引导过程继续之前,警告必须显示至少10秒。如果设备没有屏幕,则必须使用其他方式来传递设备正在引导自定义操作系统(灯条、LED等)。
  • 如果设备是UNLOCKED,则不需要检查用于对OS进行签名的密钥,也不需要在设备上检查或更新回滚stored_rollback_index[n]。因此,必须始终向用户显示关于未发生验证的警告。
    • 它取决于设备的外形和预期用途,取决于设备是如何实现的。如果设备具有屏幕和按钮(例如手机),则警告将在引导过程继续之前显示至少10秒。如果设备没有屏幕,则必须使用其他方式来传达设备已解锁(灯条,LED等)。
处理dm-verity错误

根据设计,HLOS检测哈希树验证错误,而不是引导加载程序。AVB提供了一种方法,用于指定如何通过avb_slot_verify()函数中的hashtree_error_mode参数处理错误。可能的值包括:

  • AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE表示HLOS将当前插槽标记为无效并将重新启动。在具有A/B的设备上,这将导致尝试引导另一个插槽(如果它被标记为可引导),或者它可能导致进入无法引导OS的模式(例如,某种形式的修复模式)。在Linux中,这需要使用CONFIG_DM_VERITY_AVB构建的内核。
  • AVB_HASHTREE_ERROR_MODE_RESTART表示操作系统将在当前槽无效的情况下重新启动。无条件地使用此模式时要小心,因为如果每次引导都遇到相同的哈希树验证错误,那么可能会进入死循环。
  • AVB_HASHTREE_ERROR_MODE_EIO表示将向应用程序返回EIO错误。
  • AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIO表示根据状态使用RESTARTEIO模式。该模式实现了一个状态机,默认情况下使用RESTART,当AVB_SLOT_VERIFY_FLAGS_RESTART_CAUSED_BY_HASHTREE_CORRUPTION传递给avb_slot_verify()时,模式转换为EIO。当检测到新的操作系统时,设备将转换回RESTART模式。
    • 要实现这个持久存储是必需的——具体来说,这意味着在AvbOps中传递的操作将需要实现read_persistent_value()write_persistent_value()操作。使用的持久值名为avb.managed_verity_mode并占用32字节的存储空间。
  • AVB_HASHTREE_ERROR_MODE_LOGGING表示将记录错误,并且可能会将损坏的数据返回给应用程序。此模式应仅用于诊断和调试。除非允许校验错误,否则不能使用它。

hashtree_error_mode中传递的值基本上只是通过androidboot.veritymodeandroidboot.veritymode.managedandroidboot.vbmeta.invalidate_on_error内核命令行参数以下列方式传递给HLOS:

valueandroidboot.veritymodeandroidboot.veritymode.managedandroidboot.vbmeta.invalidate_on_error
AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATEenforcing(unset)yes
AVB_HASHTREE_ERROR_MODE_RESTARTenforcing(unset)(unset)
AVB_HASHTREE_ERROR_MODE_EIOeio(unset)(unset)
AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIOeio or enforcingyes(unset)
AVB_HASHTREE_ERROR_MODE_LOGGINGignore_corruption(unset)(unset)

此表的唯一例外是,如果在顶级vbmeta中设置了AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED标志,则androidboot.veritymode需要设置为disabledandroidboot.veritymode.managedandroidboot.vbmeta.invalidate_on_error不需要设置。

应该为我的设备使用哪种模式?

这完全取决于设备,设备的使用方式以及所需的用户体验。

对于Android设备,应使用AVB_HASHTREE_ERROR_MODE_MANAGED_RESTART_AND_EIO模式。 另请参阅source.android.com上的Boot Flow章节,了解引导加载程序应实现的UX和UI类型。

如果设备没有屏幕或者HLOS同时支持多个可引导插槽,则使用AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE可能更有意义。

Android特定集成

在Android上,引导加载程序必须在内核命令行上设置androidboot.verifiedbootstate参数以标明引导状态。它应使用以下值:

  • green: 如果设备已LOCKED且未使用可由用户设置的信任根。
  • yellow: 如果设备已LOCKED且使用了可由用户设置的信任根。
  • orange: 如果设备已UNLOCKED
特定设备注意事项

本节包含有关如何将AVB集成到特定设备的信息。这并不是一个详尽的清单。

Pixel 2

在Pixel 2和Pixel 2 XL上,引导加载程序支持名为avb_custom_key的虚拟分区。仅在UNLOCKED状态下可以擦除并重写该分区。设置自定义密钥的方式如下:

avbtool extract_public_key --key key.pem --output pkmd.bin
fastboot flash avb_custom_key pkmd.bin
Shell
Copy

擦除密钥是通过擦除虚拟分区来完成的:

fastboot erase avb_custom_key
Shell
Copy

设置自定义密钥并且设备处于LOCKED状态时,它将启动使用内置密钥和自定义密钥签名的映像。所有其他安全功能(包括回滚保护)都有效,唯一的区别是使用的信任根不同。

启动使用自定义密钥签名的映像时,引导过程中屏幕将显示为黄色以提醒用户正在使用自定义密钥。

历史版本

1.1版本

1.1版增加了对以下内容的支持:

  • 将32位标志元素添加到哈希和哈希树描述符中。
  • 支持不使用A/B的分区。
  • 防篡改的持久值。
  • 哈希或哈希树描述符的持久摘要。
1.0版本

1.0支持未在更高版本中明确列出的所有功能。

文章翻译自:https://android.googlesource.com/platform/external/avb/+/master/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值