動態系統更新

動態系統更新 (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 元數據