動態系統更新 (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 上發布它。這個應用程序的目的是:
- 使用供應商定義的方案獲取圖像列表和相應的 URL。
- 將列表中的圖像與設備匹配,並顯示兼容的圖像供用戶選擇。
像這樣調用
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 加載器,它是開發人員設置中的前端。
圖 1.啟動 DSU 加載程序
當開發人員單擊DSU 加載器按鈕時,它會從 Web 獲取預配置的 DSU JSON 描述符,並在浮動菜單中顯示所有適用的圖像。選擇一個圖像開始 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 存儲控制台添加/刪除/更改發布的系統映像。
圖像必須是公共訪問,如下所示:
圖 4. GCE 中的公共訪問
Google Cloud 文檔中提供了公開項目的過程。
ZIP 文件中的多分區 DSU
從 Android 11 開始,DSU 可以有多個分區。例如,除了system.img
之外,它還可以包含product.img
。當設備啟動時,第一階段init
會檢測已安裝的 DSU 分區並在啟用已安裝的 DSU 時臨時替換設備上的分區。 DSU 包可能包含在設備上沒有對應分區的分區。
圖 5.具有多個分區的 DSU 進程
OEM 簽署的 DSU
為確保設備上運行的所有映像都得到設備製造商的授權,必須對 DSU 包中的所有映像進行簽名。例如,假設有一個 DSU 包包含兩個分區映像,如下所示:
dsu.zip {
- system.img
- product.img
}
system.img
和product.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 包。在圖像原語中有幾個屬性:
name
和details
屬性是顯示在對話框中供用戶選擇的字符串。cpu_api
、vndk
和os_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_version
是10
,對於 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 以刪除洩露的密鑰。除了更新引導分區之外,您還可以使用來自 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 中。
添加預構建模塊以復制
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)
使 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.img
和product.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 所示。
圖 7.鏈接已發布的 DSU 元數據