動態系統更新

動態系統更新 (DSU) 允許您製作 Android 系統映像,用戶可以從 Internet 下載並試用,而不會有損壞當前系統映像的風險。本文檔描述瞭如何支持 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:encryption=ice
  • 使用 ext4:
    • 135s,8G用戶,867M系統,文件系統類型:ext4:encryption=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) 和主機映像。

網守 HAL

Gatekeeper HAL需要支持較大的USER_ID值,因為 GSI 將 UID 偏移到 HAL +1000000。

驗證啟動

如果您想支持在LOCKED 狀態下啟動Developer GSI 映像而不禁用已驗證啟動,請通過將以下行添加到文件device/<device_name>/device.mk來包含 Developer 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 或使用 bootloader 的 bootconfig: androidboot.system.security_patch=2019-04-05

硬件要求

啟動 DSU 實例時,會分配兩個臨時文件:

  • 用於存儲GSI.img (1~1.5 G) 的邏輯分區
  • 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 加載器按鈕時,它會從 Web 獲取預配置的 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 Compute Engine (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 包:

  • 方法一:重用原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 可能會提供 JSON 元數據,其中包括 GSI 以及 OEM 專有圖像,如下所示:

{
    "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 元數據