動態系統更新

動態系統更新 (DSU) 可讓您製作 Android 系統映像檔,使用者可以從網際網路下載並試用,不必擔心現有系統映像檔損毀。本文說明如何支援 DSU。

核心需求

如需核心需求,請參閱「實作動態分割區」。

此外,DSU 依賴裝置對應程式驗證 (dm-verity) 核心功能,驗證 Android 系統映像檔。因此您必須啟用下列核心設定:

  • CONFIG_DM_VERITY=y
  • CONFIG_DM_VERITY_FEC=y

分區需求

自 Android 11 起,DSU 必須使用 F2FS 或 ext4 檔案系統的 /data 分區。F2FS 的效能較佳,因此建議使用,但差異應該不大。

以下列舉幾個 Pixel 裝置的動態系統更新時間範例:

  • 使用 F2FS:
    • 109 秒、8G 使用者、867M 系統、檔案系統類型:F2FS: encryption=aes-256-xts:aes-256-cts
    • 104s、8G 使用者、867M 系統、檔案系統類型:F2FS:encryption=ice
  • 使用 ext4:
    • 135 秒、8G 使用者、867M 系統、檔案系統類型: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

weaver HAL 提供固定數量的儲存空間,可存放使用者金鑰。DSU 會耗用兩個額外的金鑰位置。如果 OEM 具有織布器 HAL,則必須有足夠的插槽,可容納一般系統映像檔 (GSI) 和主機映像檔。

Gatekeeper HAL

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

驗證開機程序

如要在鎖定狀態下支援啟動開發人員 GSI 映像檔,且不需停用驗證啟動,請加入開發人員 GSI 金鑰,方法是在 device/<device_name>/device.mk 檔案中新增下列程式碼:

$(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 GB)
  • 8 GB 的空白 /data 分割區,做為執行 GSI 的沙箱

建議您在啟動 DSU 執行個體前,保留至少 10 GB 的可用空間。DSU 也支援從 SD 卡配置。如果裝置有 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. 啟用功能旗標

如果裝置執行的是使用者版本,可能無法使用功能旗標使用者介面。在這種情況下,請改用 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),以私密金鑰簽署套件,並以公開金鑰驗證套件。第一階段的 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_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 金鑰組遭到入侵,請盡快更新 RAM 磁碟,移除遭入侵的金鑰。除了更新啟動分區,您也可以使用 HTTPS URL 中的 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 功能的原始設備製造商可以提供及維護自己的金鑰黑名單。這樣一來,原始設備製造商就能封鎖特定公開金鑰,而不必更新裝置的 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 格式產生 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 檔案,請略過這個步驟。

將配對公鑰新增至 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 公開金鑰屬性

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 套件:

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 中繼資料