動態系統更新

動態系統更新 (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:
    • 109 秒、8 GB 使用者、867 MB 系統、檔案系統類型: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

總機人員 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.5 G)
  • 8 GB 的空白 /data 分區,做為執行 GSI 的沙箱

建議您先保留至少 10 GB 的可用空間,再啟動 DSU 執行個體。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 載入器」按鈕時,會擷取預先設定好的 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 的系統映像檔 (選用)

系統映像檔的儲存位置之一是 Compute Engine (GCE) 值區。發布管理員會使用 GCP 儲存空間控制台 新增/刪除/變更發布的系統映像檔

圖片必須開放公開存取,如下所示:

GCE 中的公開存取權

圖 4. GCE 中的公開存取權

如要將項目設為公開,請參閱 Google Cloud 說明文件

ZIP 檔案中的多重分割 DSU

自 Android 11 起,DSU 可擁有多個分區。例如,除了product.img system.img。裝置啟動時,第一個階段 init 會偵測 安裝 DSU 分區,並暫時取代裝置端的分區, 已安裝的 DSU 已啟用DSU 套件可能包含裝置上沒有對應分割區的分割區。

含有多個分區的 DSU 程序

圖 5. 含有多個分區的 DSU 程序

由 OEM 簽署的 DSU

為確保在裝置上執行的所有映像檔均獲得裝置製造商授權,DSU 套件中的所有映像檔都必須簽署。舉例來說,假設有一個 DSU 套件,其中包含兩個分區映像檔,如下所示:

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

system.imgproduct.img 必須先使用 OEM 金鑰簽署 儲存至 ZIP 檔案常見做法是使用非對稱式 演算法,以 RSA 為例,其密鑰是簽署套件, 公開金鑰的用途是驗證金鑰。第一個階段的 RAM 磁碟必須包含配對公鑰,例如 /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 套件如果有指定的話,DSU 載入程式會檢查該數字是否 從 ro.vndk.version 系統屬性擷取而來。

基於安全考量,撤銷 DSU 金鑰

在極少數的情況下,用於簽署 DSU 圖像的 RSA 金鑰組 則應盡快更新 ramdisk 程序,以移除 遭盜用的金鑰。除了更新啟動分區外, 透過 HTTPS 的 DSU 金鑰撤銷清單 (索引鍵黑名單) 成功入侵的金鑰 網址。

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) 就能在不更新裝置的 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 公開金鑰」一節。
  • status 表示金鑰的撤銷狀態。目前 支援的值為 REVOKED
  • reason:(選用) 描述撤銷原因的字串。

DSU 程序

本節說明如何執行幾個 DSU 設定程序。

產生新的金鑰組

使用 openssl 指令,以 .pem 格式 (例如大小為 2048 位元) 產生 RSA 私密/公開金鑰組:

$ openssl genrsa -out oem_cert_pri.pem 2048
$ openssl rsa -in oem_cert_pri.pem -pubout -out oem_cert_pub.pem

私密金鑰可能無法存取,只會保存在 硬體安全性模組 (HSM)。 在這種情況下,產生金鑰後可能會有 x509 公開金鑰憑證。請參閱將配對 pubkey 新增至 ramdisk 一節。 一節,瞭解如何從 x509 憑證產生 AVB 公開金鑰。

如何將 x509 憑證轉換為 PEM 格式:

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

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

將配對 pubkey 新增至 ramdisk

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.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)
    
  2. 讓 droidcore 目標依附於新增的 oem_cert.avbpubkey

    droidcore: oem_cert.avbpubkey
    

在 JSON 描述元中產生 AVB 公鑰屬性

oem_cert.avbpubkey 為 AVB 公開金鑰二進位格式。使用 SHA-1 即可 將其轉換為 JSON 描述元前的可讀性:

$ 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.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"
      },

}

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

鏈結已發布的 DSU 中繼資料

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