动态系统更新 (DSU) 允许您制作 Android 系统映像,用户可以从互联网下载并试用该映像,而无需担心损坏当前系统映像的风险。本文档描述了如何支持 DSU。
内核要求
有关内核要求,请参阅实现动态分区。
此外,DSU 依赖 device-mapper-verity (dm-verity) 内核功能来验证 Android 系统映像。因此,您必须启用以下内核配置:
-
CONFIG_DM_VERITY=y
-
CONFIG_DM_VERITY_FEC=y
分区要求
从 Android 11 开始,DSU 需要/data
分区才能使用 F2FS 或 ext4 文件系统。 F2FS 提供更好的性能,建议使用,但差异应该不大。
以下是 Pixel 设备动态系统更新所需时间的一些示例:
- 使用 F2FS:
- 109s,8G用户,867M系统,文件系统类型:F2FS:加密=aes-256-xts:aes-256-cts
- 104s,8G用户,867M系统,文件系统类型:F2FS:加密=ice
- 使用 ext4:
- 135s,8G用户,867M系统,文件系统类型:ext4:加密=aes-256-xts:aes-256-cts
如果在您的平台上需要更长的时间,您可能需要检查挂载标志是否包含任何使“同步”写入的标志,或者您可以显式指定“异步”标志以获得更好的性能。
需要metadata
分区(16 MB 或更大)来存储与已安装映像相关的数据。它必须在第一阶段安装期间安装。
userdata
分区必须使用F2FS或ext4文件系统。使用 F2FS 时,请包含Android 通用内核中可用的所有 F2FS 相关补丁。
DSU 是使用 kernel/common 4.9 开发和测试的。建议使用内核 4.9 及更高版本来实现此功能。
供应商 HAL 行为
韦弗·哈尔
Weaver HAL 提供固定数量的插槽来存储用户密钥。 DSU 消耗两个额外的密钥槽。如果 OEM 有 weaver HAL,则需要有足够的插槽用于通用系统映像 (GSI) 和主机映像。
看门人哈尔
Gatekeeper HAL需要支持大的USER_ID
值,因为 GSI 将 UID 偏移到 HAL +1000000。
验证启动
如果您希望支持在锁定状态下启动开发人员 GSI 映像而不禁用验证启动,请通过将以下行添加到文件device/<device_name>/device.mk
来包含开发人员 GSI 密钥:
$(call inherit-product, $(SRC_TARGET_DIR)/product/developer_gsi_keys.mk)
回滚保护
使用 DSU 时,下载的 Android 系统映像必须比设备上当前的系统映像更新。这是通过比较两个系统映像的 Android 验证启动(AVB) AVB 属性描述符中的安全补丁级别来完成的: Prop: com.android.build.system.security_patch -> '2019-04-05'
。
对于不使用 AVB 的设备,请将当前系统映像的安全补丁级别放入内核 cmdline 或使用引导加载程序的 bootconfig: androidboot.system.security_patch=2019-04-05
。
硬件要求
当您启动 DSU 实例时,会分配两个临时文件:
- 存储
GSI.img
逻辑分区(1~1.5G) - 8 GB 空
/data
分区作为运行 GSI 的沙箱
我们建议在启动 DSU 实例之前预留至少 10 GB 的可用空间。 DSU 还支持从 SD 卡分配。当存在 SD 卡时,它具有最高的分配优先级。 SD 卡支持对于可能没有足够内部存储空间的低功耗设备至关重要。如果存在 SD 卡,请确保未采用该卡。 DSU 不支持采用的 SD 卡。
可用的前端
您可以使用adb
、OEM 应用或一键式 DSU 加载程序(在 Android 11 或更高版本中)启动 DSU。
使用 adb 启动 DSU
要使用 adb 启动 DSU,请输入以下命令:
$ simg2img out/target/product/.../system.img system.raw
$ gzip -c system.raw > system.raw.gz
$ adb push system.raw.gz /storage/emulated/0/Download
$ adb shell am start-activity \
-n com.android.dynsystem/com.android.dynsystem.VerificationActivity \
-a android.os.image.action.START_INSTALL \
-d file:///storage/emulated/0/Download/system.raw.gz \
--el KEY_SYSTEM_SIZE $(du -b system.raw|cut -f1) \
--el KEY_USERDATA_SIZE 8589934592
使用应用程序启动 DSU
DSU 的主要入口点是android.os.image.DynamicSystemClient.java
API:
public class DynamicSystemClient {
...
...
/**
* Start installing DynamicSystem from URL with default userdata size.
*
* @param systemUrl A network URL or a file URL to system image.
* @param systemSize size of system image.
*/
public void start(String systemUrl, long systemSize) {
start(systemUrl, systemSize, DEFAULT_USERDATA_SIZE);
}
您必须在设备上捆绑/预安装此应用程序。由于DynamicSystemClient
是系统 API,因此您无法使用常规 SDK API 构建应用程序,也无法将其发布到 Google Play 上。这个应用程序的目的是:
- 使用供应商定义的方案获取图像列表和相应的 URL。
- 将列表中的图像与设备进行匹配,并显示兼容的图像供用户选择。
像这样调用
DynamicSystemClient.start
:DynamicSystemClient aot = new DynamicSystemClient(...) aot.start( ...URL of the selected image..., ...uncompressed size of the selected image...);
该 URL 指向经过 gzip 压缩的非稀疏系统映像文件,您可以使用以下命令创建该文件:
$ simg2img ${OUT}/system.img ${OUT}/system.raw
$ gzip ${OUT}/system.raw
$ ls ${OUT}/system.raw.gz
文件名应遵循以下格式:
<android version>.<lunch name>.<user defined title>.raw.gz
例子:
-
o.aosp_taimen-userdebug.2018dev.raw.gz
-
p.aosp_taimen-userdebug.2018dev.raw.gz
一键式 DSU 加载程序
Android 11 引入了一键式 DSU 加载器,它是开发人员设置中的前端。
图 1.启动 DSU 加载程序
当开发人员单击DSU 加载器按钮时,它会从网络获取预配置的 DSU JSON 描述符,并在浮动菜单中显示所有适用的图像。选择一个镜像开始DSU安装,进度显示在通知栏上。
图 2. DSU 映像安装进度
默认情况下,DSU 加载程序加载包含 GSI 图像的 JSON 描述符。以下部分演示如何制作 OEM 签名的 DSU 包并从 DSU 加载程序加载它们。
特征标志
DSU 功能位于settings_dynamic_android
功能标志下。在使用 DSU 之前,请确保相应的功能标志已启用。
图 3.启用功能标志
功能标志 UI 在运行用户版本的设备上可能不可用。在这种情况下,请改用adb
命令:
$ adb shell setprop persist.sys.fflag.override.settings_dynamic_system 1
GCE 上的供应商主机系统映像(可选)
系统映像的可能存储位置之一是 Google 计算引擎 (GCE) 存储桶。发布管理员使用GCP存储控制台添加/删除/更改已发布的系统映像。
这些图像必须是公开访问的,如下所示:
图 4. GCE 中的公共访问
Google Cloud 文档中提供了公开项目的过程。
ZIP 文件中的多分区 DSU
从 Android 11 开始,DSU 可以有多个分区。例如,除了system.img
之外,它还可以包含product.img
。当设备启动时,第一阶段init
会检测已安装的 DSU 分区,并在启用已安装的 DSU 时临时替换设备上的分区。 DSU 包中可能包含一个在设备上没有对应分区的分区。
图 5.具有多个分区的 DSU 进程
OEM 签署的 DSU
为了确保设备上运行的所有映像均获得设备制造商的授权,DSU 包中的所有映像都必须经过签名。例如,假设有一个 DSU 包包含两个分区映像,如下所示:
dsu.zip {
- system.img
- product.img
}
system.img
和product.img
在放入 ZIP 文件之前都必须由 OEM 密钥签名。通常的做法是使用非对称算法,例如RSA,其中秘密密钥用于对包进行签名,公钥用于验证它。第一阶段 ramdisk 必须包含配对公钥,例如/avb/*.avbpubkey
。如果设备已经采用了 AVB,则现有的签名程序就足够了。以下部分说明了签名过程,并重点介绍了用于验证 DSU 包中映像的 AVB 公钥的位置。
DSU JSON 描述符
DSU JSON 描述符描述 DSU 包。它支持两个原语。首先, include
原语包含额外的 JSON 描述符或将 DSU 加载程序重定向到新位置。例如:
{
"include": ["https://.../gsi-release/gsi-src.json"]
}
其次, image
基元用于描述已发布的 DSU 包。图像基元内部有几个属性:
name
和details
属性是显示在对话框上供用户选择的字符串。cpu_api
、vndk
和os_version
属性用于兼容性检查,这将在下一节中介绍。可选的
pubkey
属性描述与用于签署 DSU 包的密钥配对的公钥。指定后,DSU 服务可以检查设备是否具有用于验证 DSU 包的密钥。这可以避免安装无法识别的 DSU 包,例如将 OEM-A 签名的 DSU 安装到 OEM-B 制造的设备上。可选的
tos
属性指向描述相应 DSU 包的服务条款的文本文件。当开发人员选择指定了服务条款属性的 DSU 软件包时,将打开图 6 所示的对话框,要求开发人员在安装 DSU 软件包之前接受服务条款。图 6.服务条款对话框
作为参考,以下是 GSI 的 DSU JSON 描述符:
{
"images":[
{
"name":"GSI+GMS x86",
"os_version":"10",
"cpu_abi": "x86",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"tos": "https://dl.google.com/developers/android/gsi/gsi-tos.txt",
"uri":"https://.../gsi/gsi_gms_x86-exp-QP1A.190711.020.C4-5928301.zip"
},
{
"name":"GSI+GMS ARM64",
"os_version":"10",
"cpu_abi": "arm64-v8a",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"tos": "https://dl.google.com/developers/android/gsi/gsi-tos.txt",
"uri":"https://.../gsi/gsi_gms_arm64-exp-QP1A.190711.020.C4-5928301.zip"
},
{
"name":"GSI ARM64",
"os_version":"10",
"cpu_abi": "arm64-v8a",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"uri":"https://.../gsi/aosp_arm64-exp-QP1A.190711.020.C4-5928301.zip"
},
{
"name":"GSI x86_64",
"os_version":"10",
"cpu_abi": "x86_64",
"details":"exp-QP1A.190711.020.C4-5928301",
"vndk":[
27,
28,
29
],
"pubkey":"",
"uri":"https://.../gsi/aosp_x86_64-exp-QP1A.190711.020.C4-5928301.zip"
}
]
}
兼容性管理
有几个属性用于指定 DSU 包和本地设备之间的兼容性:
cpu_api
是描述设备架构的字符串。该属性是强制性的,并且与ro.product.cpu.abi
系统属性进行比较。它们的值必须完全匹配。os_version
是一个可选整数,指定 Android 版本。例如,对于 Android 10,os_version
为10
,对于 Android 11,os_version
为11
。当指定此属性时,它必须等于或大于ro.system.build.version.release
系统属性。此检查用于防止在 Android 11 供应商设备上启动 Android 10 GSI 映像,目前尚不支持该设备。允许在 Android 10 设备上启动 Android 11 GSI 映像。vndk
是一个可选数组,指定 DSU 包中包含的所有 VNDK。指定后,DSU 加载程序会检查是否包含从ro.vndk.version
系统属性中提取的数字。
出于安全考虑撤销 DSU 密钥
在极少数情况下,当用于签署 DSU 映像的 RSA 密钥对遭到泄露时,应尽快更新 ramdisk 以删除泄露的密钥。除了更新启动分区之外,您还可以使用来自 HTTPS URL 的 DSU 密钥吊销列表(密钥黑名单)来阻止受损的密钥。
DSU 密钥撤销列表包含已撤销的 AVB 公钥列表。在 DSU 安装期间,DSU 映像内的公钥将通过吊销列表进行验证。如果发现映像包含已撤销的公钥,则 DSU 安装过程将停止。
密钥撤销列表URL应为HTTPS URL以保证安全强度,并在资源字符串中指定:
frameworks/base/packages/DynamicSystemInstallationService/res/values/strings.xml@key_revocation_list_url
该字符串的值为https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json
,它是 Google 发布的 GSI 密钥的撤销列表。该资源字符串可以叠加和定制,以便采用DSU功能的OEM可以提供并维护自己的密钥黑名单。这为 OEM 提供了一种在不更新设备的 ramdisk 映像的情况下阻止某些公钥的方法。
撤销列表的格式为:
{
"entries":[
{
"public_key":"bf14e439d1acf231095c4109f94f00fc473148e6",
"status":"REVOKED",
"reason":"Key revocation test key"
},
{
"public_key":"d199b2f29f3dc224cca778a7544ea89470cbef46",
"status":"REVOKED",
"reason":"Key revocation test key"
}
]
}
-
public_key
是已撤销密钥的 SHA-1 摘要,其格式在生成 AVB 公钥部分中描述。 -
status
表示密钥的撤销状态。目前,唯一支持的值是REVOKED
。 -
reason
是一个可选字符串,描述撤销原因。
DSU 程序
本节介绍如何执行多个 DSU 配置过程。
生成新的密钥对
使用openssl
命令生成.pem
格式的 RSA 私钥/公钥对(例如,大小为 2048 位):
$ openssl genrsa -out oem_cert_pri.pem 2048
$ openssl rsa -in oem_cert_pri.pem -pubout -out oem_cert_pub.pem
私钥可能无法访问,并且仅保存在硬件安全模块 (HSM)中。在这种情况下,密钥生成后可能有可用的 x509 公钥证书。有关从 x509 证书生成 AVB 公钥的说明,请参阅将配对公钥添加到 ramdisk部分。
要将 x509 证书转换为 PEM 格式:
$ openssl x509 -pubkey -noout -in oem_cert_pub.x509.pem > oem_cert_pub.pem
如果证书已经是 PEM 文件,请跳过此步骤。
将配对公钥添加到 ramdisk
oem_cert.avbpubkey
必须放在/avb/*.avbpubkey
下以验证签名的 DSU 包。首先将PEM格式的公钥转换为AVB公钥格式:
$ avbtool extract_public_key --key oem_cert_pub.pem --output oem_cert.avbpubkey
然后按照以下步骤将公钥包含在第一阶段 ramdisk 中。
添加预构建模块以复制
avbpubkey
。例如,添加device/<company>/<board>/oem_cert.avbpubkey
和device/<company>/<board>/avb/Android.mk
内容如下:include $(CLEAR_VARS) LOCAL_MODULE := oem_cert.avbpubkey LOCAL_MODULE_CLASS := ETC LOCAL_SRC_FILES := $(LOCAL_MODULE) ifeq ($(BOARD_USES_RECOVERY_AS_BOOT),true) LOCAL_MODULE_PATH := $(TARGET_RECOVERY_ROOT_OUT)/first_stage_ramdisk/avb else LOCAL_MODULE_PATH := $(TARGET_RAMDISK_OUT)/avb endif include $(BUILD_PREBUILT)
使 droidcore 目标依赖于添加的
oem_cert.avbpubkey
:droidcore: oem_cert.avbpubkey
在 JSON 描述符中生成 AVB pubkey 属性
oem_cert.avbpubkey
采用 AVB 公钥二进制格式。在将其放入 JSON 描述符之前,使用 SHA-1 使其可读:
$ sha1sum oem_cert.avbpubkey | cut -f1 -d ' '
3e62f2be9d9d813ef5........866ac72a51fd20
这将是 JSON 描述符的pubkey
属性的内容。
"images":[
{
...
"pubkey":"3e62f2be9d9d813ef5........866ac72a51fd20",
...
},
签署 DSU 包
使用以下方法之一对 DSU 包进行签名:
方法1:重用原始AVB签名过程制作的工件来制作DSU包。另一种方法是从发布包中提取已签名的映像,并使用提取的映像直接制作 ZIP 文件。
方法 2:如果私钥可用,请使用以下命令对 DSU 分区进行签名。 DSU 包(ZIP 文件)中的每个
img
均单独签名:$ key_len=$(openssl rsa -in oem_cert_pri.pem -text | grep Private-Key | sed -e 's/.*(\(.*\) bit.*/\1/') $ for partition in system product; do avbtool add_hashtree_footer \ --image ${OUT}/${partition}.img \ --partition_name ${partition} \ --algorithm SHA256_RSA${key_len} \ --key oem_cert_pri.pem done
有关使用avbtool
添加add_hashtree_footer
更多信息,请参阅使用 avbtool 。
本地验证 DSU 包
建议使用以下命令根据配对公钥验证所有本地映像:
for partition in system product; do
avbtool verify_image --image ${OUT}/${partition}.img --key oem_cert_pub.pem
done
预期输出如下所示:
Verifying image dsu/system.img using key at oem_cert_pub.pem
vbmeta: Successfully verified footer and SHA256_RSA2048 vbmeta struct in dsu/system.img
: Successfully verified sha1 hashtree of dsu/system.img for image of 898494464 bytes
Verifying image dsu/product.img using key at oem_cert_pub.pem
vbmeta: Successfully verified footer and SHA256_RSA2048 vbmeta struct in dsu/product.img
: Successfully verified sha1 hashtree of dsu/product.img for image of 905830400 bytes
制作 DSU 包
以下示例创建一个包含system.img
和product.img
的 DSU 包:
dsu.zip {
- system.img
- product.img
}
对两个映像进行签名后,使用以下命令制作 ZIP 文件:
$ mkdir -p dsu
$ cp ${OUT}/system.img dsu
$ cp ${OUT}/product.img dsu
$ cd dsu && zip ../dsu.zip *.img && cd -
自定义一键式 DSU
默认情况下,DSU 加载程序指向 GSI 图像的元数据,即https://...google.com/.../gsi-src.json
。
OEM 可以通过定义指向其自己的 JSON 描述符的persist.sys.fflag.override.settings_dynamic_system.list
属性来覆盖该列表。例如,OEM 可能会提供包含 GSI 以及 OEM 专有图像的 JSON 元数据,如下所示:
{
"include": ["https://dl.google.com/.../gsi-src.JSON"]
"images":[
{
"name":"OEM image",
"os_version":"10",
"cpu_abi": "arm64-v8a",
"details":"...",
"vndk":[
27,
28,
29
],
"spl":"...",
"pubkey":"",
"uri":"https://.../....zip"
},
}
OEM 可以链接已发布的 DSU 元数据,如图 7 所示。
图 7.链接已发布的 DSU 元数据