动态系统更新

动态系统更新 (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 上。这个应用程序的目的是:

  1. 使用供应商定义的方案获取图像列表和相应的 URL。
  2. 将列表中的图像与设备进行匹配,并显示兼容的图像供用户选择。
  3. 像这样调用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 加载器,它是开发人员设置中的前端。

启动 DSU 加载程序

图 1.启动 DSU 加载程序

当开发人员单击DSU 加载器按钮时,它会从网络获取预配置的 DSU JSON 描述符,并在浮动菜单中显示所有适用的图像。选择一个镜像开始DSU安装,进度显示在通知栏上。

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存储控制台添加/删除/更改已发布的系统映像。

这些图像必须是公开访问的,如下所示:

GCE 中的公共访问

图 4. GCE 中的公共访问

Google Cloud 文档中提供了公开项目的过程。

ZIP 文件中的多分区 DSU

从 Android 11 开始,DSU 可以有多个分区。例如,除了system.img之外,它还可以包含product.img 。当设备启动时,第一阶段init会检测已安装的 DSU 分区,并在启用已安装的 DSU 时临时替换设备上的分区。 DSU 包中可能包含一个在设备上没有对应分区的分区。

具有多个分区的 DSU 进程

图 5.具有多个分区的 DSU 进程

OEM 签署的 DSU

为了确保设备上运行的所有映像均获得设备制造商的授权,DSU 包中的所有映像都必须经过签名。例如,假设有一个 DSU 包包含两个分区映像,如下所示:

dsu.zip {
    - system.img
    - product.img
}

system.imgproduct.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 包。图像基元内部有几个属性:

  • namedetails属性是显示在对话框上供用户选择的字符串。

  • cpu_apivndkos_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_version10 ,对于 Android 11, os_version11 。当指定此属性时,它必须等于或大于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 中。

  1. 添加预构建模块以复制avbpubkey 。例如,添加device/<company>/<board>/oem_cert.avbpubkeydevice/<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)
    
  2. 使 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.imgproduct.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 所示。

链接已发布的 DSU 元数据

图 7.链接已发布的 DSU 元数据