動態系統更新

動態系統更新 (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:encryption=aes-256-xts:aes-256-cts
    • 104 秒,8G 使用者,867M,檔案系統類型:F2FS: encryption=ice
  • 使用 ext4:
    • 135 秒、8 GB 使用者、867 MB 系統、檔案系統類型:ext4:encryption=aes-256-xts:aes-256-cts

如果在您的平台上耗費的時間過長,建議您檢查掛載標記是否包含任何會執行「同步」寫入作業的標記,或者您可以明確指定「非同步」標記,以獲得更佳的效能。

必須使用 metadata 分區 (16 MB 以上),才能儲存與已安裝映像檔相關的資料。必須在第一個階段掛接時進行掛接。

userdata 分區必須使用 F2FS 或 ext4 檔案系統。使用 F2FS 時,請加入 Android 通用核心中所有可用的 F2FS 相關修補程式。

DSU 是使用核心/通用 4.9 開發及測試。建議使用核心 4.9 以上版本來使用這項功能。

供應商 HAL 行為

Weaver HAL

武器 HAL 提供固定數量的運算單元,用於儲存使用者金鑰。DSU 會使用兩個額外的鍵盤插槽。如果原始設備製造商 (OEM) 有 weaver HAL,就必須有足夠的插槽可供一般系統映像檔 (GSI) 和主機映像檔使用。

總機 HAL

Gatekeeper HAL 需要支援大型 USER_ID 值,因為 GSI 會將 UID 偏移 +1000000 至 HAL。

驗證啟動程序

如果您想在不停用驗證開機程序的情況下,支援以鎖定狀態啟動開發人員 GSI 映像檔的功能,請在 device/<device_name>/device.mk 檔案中加入下列程式碼,加入開發人員 GSI 金鑰:

$(call inherit-product, $(SRC_TARGET_DIR)/product/developer_gsi_keys.mk)

復原保護

使用 DSU 時,下載的 Android 系統映像檔必須比裝置上的目前系統映像檔更新。方法是比較兩個系統映像檔 (Prop: com.android.build.system.security_patch -> '2019-04-05') 的 Android 驗證開機程序 (AVB) AVB 屬性描述項中的安全性修補程式等級。

如果裝置未使用 AVB,請使用系統啟動載入程式,將目前系統映像檔的安全性修補程式等級或系統啟動載入程式放入核心 cmdline 或 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 卡,請確認該 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. 使用供應商定義的配置,擷取圖片清單和對應的網址。
  2. 將清單中的圖片與裝置進行比對,並顯示相容的圖片供使用者選取。
  3. 請按以下方式叫用 DynamicSystemClient.start

    DynamicSystemClient aot = new DynamicSystemClient(...)
       aot.start(
            ...URL of the selected image...,
            ...uncompressed size of the selected image...);
    
    

使用下列指令,網址會指向 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 Loader」按鈕時,系統會從網路擷取預先設定的 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 都必須由原始設備製造商 (OEM) 金鑰簽署,然後才能放入 ZIP 檔案。一般做法是使用非對稱式演算法,例如 RSA,其中私密金鑰用於簽署套件,而公開金鑰則用於驗證。第一個階段 ramdisk 必須包含配對公開金鑰,例如 /avb/*.avbpubkey。如果裝置已採用 AVB,現有的簽署程序就足以處理。以下各節說明簽署程序,並醒目顯示用於驗證 DSU 套件中圖片的 AVB pubkey 的位置。

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_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 應盡快更新,以移除遭駭金鑰。除了更新啟動分區,您還可以使用 DSU 金鑰撤銷清單 (按鍵黑名單),從 HTTPS 網址封鎖遭入侵的金鑰。

DSU 金鑰撤銷清單包含已撤銷的 AVB 公開金鑰。在 DSU 安裝期間,系統會使用撤銷清單驗證 DSU 映像檔中的公開金鑰。如果發現映像檔包含已撤銷的公開金鑰,DSU 安裝程序就會停止。

金鑰撤銷清單的網址應為 HTTPS 網址,以便確保安全性。此網址會在資源字串中指定:

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) 就能在不更新裝置的 RAM 磁碟映像檔的情況下,封鎖特定公開金鑰。

撤銷清單的格式如下:

{
   "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 pubkey 一節所述的格式。
  • 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 公開金鑰,請參閱「將配對公開金鑰新增至 RAM 磁碟」一節。

如要將 x509 憑證轉換為 PEM 格式,請按照下列步驟操作:

$ openssl x509 -pubkey -noout -in oem_cert_pub.x509.pem > oem_cert_pub.pem

如果憑證已經是 PEM 檔案,請略過這個步驟。

將配對公開金鑰新增至 RAM 磁碟

oem_cert.avbpubkey 必須放在 /avb/*.avbpubkey 底下,才能驗證已簽署的 DSU 套件。首先,將 PEM 格式的公開金鑰轉換為 AVB 公開金鑰格式:

$ avbtool extract_public_key --key oem_cert_pub.pem --output oem_cert.avbpubkey

然後按照下列步驟,在第一階段的 RAM 磁碟中加入公開金鑰。

  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 公開金鑰二進位格式。將 SHA-1 轉換為 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"
      },

}

如圖 7 所示,原始設備製造商 (OEM) 可鏈結已發布的 DSU 中繼資料。

鏈結已發布的 DSU 中繼資料

圖 7. 鏈結已發布的 DSU 中繼資料