APEX 文件格式

Android Pony EXpress (APEX) 容器格式是在 Android 10 中引入的,它用於較低級別系統模塊的安裝流程中。這種格式有助於更新不適合標準 Android 應用程序模型的系統組件。一些示例性的組件是本地服務和庫,硬件抽象層( HAL中),運行時間( ART ),和類庫。

術語“APEX”也可以指 APEX 文件。

背景

儘管 Android 支持通過包安裝程序應用程序(例如 Google Play 商店應用程序)更新適合標準應用程序模型(例如,服務、活動)的模塊,但對較低級別的操作系統組件使用類似模型具有以下缺點:

  • 不能在啟動序列的早期使用基於 APK 的模塊。包管理器是有關應用程序信息的中央存儲庫,只能從活動管理器啟動,在啟動過程的後期準備就緒。
  • APK 格式(尤其是清單)是為 Android 應用程序設計的,系統模塊並不總是合適的。

設計

本節介紹 APEX 文件格式和 APEX 管理器的高級設計,APEX 管理器是管理 APEX 文件的服務。

對於為什麼選擇這樣的設計對於APEX的更多信息,請參見替代認為開發APEX時

APEX 格式

這是 APEX 文件的格式。

APEX 文件格式

圖1. APEX文件格式

在頂層,APEX 文件是一個 zip 文件,其中文件以未壓縮的方式存儲並位於 4 KB 邊界處。

APEX 文件中的四個文件是:

  • apex_manifest.json
  • AndroidManifest.xml
  • apex_payload.img
  • apex_pubkey

apex_manifest.json文件包含包名稱和版本,其標識APEX文件。

AndroidManifest.xml文件允許APEX文件使用APK相關的工具和基礎設施,如亞洲開發銀行,PackageManager,和包安裝的應用程序(如Play商店)。例如,APEX文件可以使用現有的工具,如aapt從文件檢查基本元數據。該文件包含包名稱和版本信息。這些信息一般同時有apex_manifest.json

apex_manifest.json建議在AndroidManifest.xml新的代碼,並與APEX處理系統。 AndroidManifest.xml可能包含可以由現有的應用程序發布工具可以使用其他定位信息。

apex_payload.img是DM-Verity的支持ext4文件系統映像。映像在運行時通過環回設備安裝。具體地,哈希樹和元數據塊被使用所創建的libavb庫。未解析文件系統有效負載(因為映像應該可以就地安裝)。常規文件都包含在裡面apex_payload.img文件。

apex_pubkey是用來簽名的文件系統映像的公共密鑰。在運行時,此密鑰可確保下載的 APEX 使用在內置分區中籤署相同 APEX 的同一實體進行簽名。

APEX 經理

APEX的經理(或apexd )負責驗證,安裝和卸載APEX文件獨立的本地處理。此過程已啟動並在引導序列的早期準備就緒。 APEX文件通常在設備上預先安裝在/system/apex 。如果沒有可用更新,APEX 管理器默認使用這些包。

一個頂點的更新序列使用PackageManager類和如下。

  1. APEX 文件是通過包安裝程序應用程序、ADB 或其他來源下載的。
  2. 包管理器開始安裝過程。在識別出文件是 APEX 後,包管理器將控制權轉移給 APEX 管理器。
  3. APEX 管理器驗證 APEX 文件。
  4. 如果驗證 APEX 文件,APEX 管理器的內部數據庫會更新以反映 APEX 文件在下次啟動時被激活。
  5. 安裝請求者在成功包驗證後接收廣播。
  6. 要繼續安裝,必須重新啟動系統。
  7. 下次啟動時,APEX 管理器啟動,讀取內部數據庫,並對列出的每個 APEX 文件執行以下操作:

    1. 驗證 APEX 文件。
    2. 從 APEX 文件創建環回設備。
    3. 在環回設備之上創建一個設備映射器塊設備。
    4. 安裝設備映射器塊設備到一個唯一的路徑(例如, /apex/ name @ ver )。

當內部數據庫中列出的所有 APEX 文件都掛載後,APEX 管理器為其他系統組件提供了一個 binder 服務,以查詢有關已安裝 APEX 文件的信息。例如,其他系統組件可以查詢設備中安裝的APEX文件列表或查詢特定APEX掛載的確切路徑,以便訪問文件。

APEX 文件是 APK 文件

APEX文件是有效的APK文件,因為他們簽訂的ZIP壓縮文件(使用APK簽名方案)包含AndroidManifest.xml文件。這允許 APEX 文件使用 APK 文件的基礎結構,例如包安裝程序應用、簽名實用程序和包管理器。

AndroidManifest.xml的APEX文件內部文件是最小的,由包的nameversionCode和可選targetSdkVersionminSdkVersionmaxSdkVersion細粒度目標。此信息允許通過現有渠道(例如包安裝程序應用程序和 ADB)交付 APEX 文件。

支持的文件類型

APEX 格式支持以下文件類型:

  • 本機共享庫
  • 本機可執行文件
  • JAR 文件
  • 數據文件
  • 配置文件

這並不意味著 APEX 可以更新所有這些文件類型。文件類型是否可以更新取決於平台以及文件類型的接口定義的穩定性。

簽約

APEX 文件以兩種方式簽名。首先, apex_payload.img (具體而言,附加到vbmeta描述符apex_payload.img )文件與密鑰簽名。然後,整個APEX使用簽名的APK簽名方案V3 。在這個過程中使用了兩個不同的密鑰。

在設備端,安裝了與用於簽署 vbmeta 描述符的私鑰相對應的公鑰。 APEX 管理器使用公鑰來驗證請求安裝的 APEX。每個 APEX 必須使用不同的密鑰簽名,並在構建時和運行時強制執行。

內置分區中的 APEX

APEX的文件可以位於內置的分區如/system 。該分區已經通過 dm-verity,因此 APEX 文件直接掛載在環回設備上。

如果內置分區中存在APEX,則可以通過提供具有相同包名稱和大於或等於版本代碼的APEX包來更新APEX。新的APEX存儲在/data和,類似的APK,新安裝的版本的陰影版本已經存在於內置的分區。但與 APK 不同的是,新安裝的 APEX 版本只有在重新啟動後才能激活。

內核要求

要在 Android 設備上支持 APEX 主線模塊,需要以下 Linux 內核功能:環回驅動程序和 dm-verity。環回驅動程序將文件系統映像掛載到 APEX 模塊中,並且 dm-verity 驗證 APEX 模塊。

當使用 APEX 模塊時,環回驅動程序和 dm-verity 的性能對於實現良好的系統性能很重要。

支持的內核版本

使用內核版本 4.4 或更高版本的設備支持 APEX 主線模塊。搭載 Android 10 或更高版本的新設備必須使用內核版本 4.9 或更高版本才能支持 APEX 模塊。

所需的內核補丁

支持 APEX 模塊所需的內核補丁包含在 Android 通用樹中。要獲取支持 APEX 的補丁,請使用最新版本的 Android 通用樹。

內核版本 4.4

此版本僅適用於從 Android 9 升級到 Android 10 並希望支持 APEX 模塊的設備。為了得到所需要的補丁,從向下合併android-4.4 ,強烈建議分支。以下是內核版本 4.4 所需的單個補丁列表。

  • 上游:循環:添加的ioctl用於改變邏輯塊大小( 4.4
  • 反向移植:塊/環:設置hw_sectors( 4.4
  • 上游:循環:在compat的IOCTL添加LOOP_SET_BLOCK_SIZE( 4.4
  • ANDROID:MNT:修復next_descendent( 4.4
  • ANDROID:MNT:重新裝入應該傳播到奴隸的奴隸( 4.4
  • ANDROID:MNT:傳播正確地重新安裝( 4.4
  • 還原“ANDROID:DM真理:添加最小取大小”( 4.4
  • 上游:循環:降緩存如果偏移量或BLOCK_SIZE被改變( 4.4

內核版本 4.9/4.14/4.19

為了獲得所需的補丁內核版本4.9 / 4.14 / 4.19,向下合併從android-common分支。

必需的內核配置選項

以下列表顯示了支持 Android 10 中引入的 APEX 模塊的基本配置要求。帶星號 (*) 的項目是 Android 9 及更低版本的現有要求。

(*) CONFIG_AIO=Y # AIO support (for direct I/O on loop devices)
CONFIG_BLK_DEV_LOOP=Y # for loop device support
CONFIG_BLK_DEV_LOOP_MIN_COUNT=16 # pre-create 16 loop devices
(*) CONFIG_CRYPTO_SHA1=Y # SHA1 hash for DM-verity
(*) CONFIG_CRYPTO_SHA256=Y # SHA256 hash for DM-verity
CONFIG_DM_VERITY=Y # DM-verity support

內核命令行參數要求

要支持 APEX,請確保內核命令行參數滿足以下要求:

  • loop.max_loop不得設置
  • loop.max_part必須<= 8

建立APEX

本節介紹如何使用 Android 構建系統構建 APEX。下面是一個例子Android.bp為APEX命名apex.test

apex {
    name: "apex.test",
    manifest: "apex_manifest.json",
    file_contexts: "file_contexts",
    // libc.so and libcutils.so are included in the apex
    native_shared_libs: ["libc", "libcutils"],
    binaries: ["vold"],
    java_libs: ["core-all"],
    prebuilts: ["my_prebuilt"],
    compile_multilib: "both",
    key: "apex.test.key",
    certificate: "platform",
}

apex_manifest.json例如:

{
  "name": "com.android.example.apex",
  "version": 1
}

file_contexts例如:

(/.*)?           u:object_r:system_file:s0
/sub(/.*)?       u:object_r:sub_file:s0
/sub/file3       u:object_r:file3_file:s0

APEX 中的文件類型和位置

文件類型在 APEX 中的位置
共享庫/lib/lib64/lib/arm在86翻譯臂)
可執行文件/bin
Java 庫/javalib
預建/etc

傳遞依賴

APEX 文件自動包含本機共享庫或可執行文件的傳遞依賴項。例如,如果libFoo取決於libBar ,當僅包括兩個庫libFoo在列native_shared_libs屬性。

處理多個 ABI

安裝native_shared_libs用於裝置的主要和輔助應用程序二進制接口(ABI的)屬性。如果 APEX 以具有單個 ABI(即僅 32 位或僅 64 位)的設備為目標,則僅安裝具有相應 ABI 的庫。

安裝binaries ,如下所述僅對於設備的主ABI屬性:

  • 如果設備僅為 32 位,則僅安裝二進製文件的 32 位變體。
  • 如果設備僅為 64 位,則僅安裝二進製文件的 64 位變體。

要通過的ABI添加本機庫和二進制文件的細粒度控制,使用multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries]屬性。

  • first :匹配裝置的主ABI。這是二進製文件的默認設置。
  • lib32 :匹配裝置的32位ABI,如果支持的話。
  • lib64 :匹配裝置的64位ABI,它支撐。
  • prefer32 :匹配裝置的32位ABI,如果支持的話。如果不支持 32 位 ABI,則匹配 64 位 ABI。
  • both :同時匹配的ABI。這是默認native_shared_libraries

javalibraries ,和prebuilts性能ABI無關。

此示例適用於支持 32/64 且不喜歡 32 的設備:

apex {
    // other properties are omitted
    native_shared_libs: ["libFoo"], // installed for 32 and 64
    binaries: ["exec1"], // installed for 64, but not for 32
    multilib: {
        first: {
            native_shared_libs: ["libBar"], // installed for 64, but not for 32
            binaries: ["exec2"], // same as binaries without multilib.first
        },
        both: {
            native_shared_libs: ["libBaz"], // same as native_shared_libs without multilib
            binaries: ["exec3"], // installed for 32 and 64
        },
        prefer32: {
            native_shared_libs: ["libX"], // installed for 32, but not for 64
        },
        lib64: {
            native_shared_libs: ["libY"], // installed for 64, but not for 32
        },
    },
}

vbmeta 簽名

使用不同的密鑰對每個 APEX 進行簽名。當需要一個新的密鑰,創建公私密鑰對,並作出apex_key模塊。使用key屬性使用該密鑰簽署APEX。公鑰被自動包含在名為APEX的avb_pubkey

# create an rsa key pair
openssl genrsa -out foo.pem 4096

# extract the public key from the key pair
avbtool extract_public_key --key foo.pem --output foo.avbpubkey

# in Android.bp
apex_key {
    name: "apex.test.key",
    public_key: "foo.avbpubkey",
    private_key: "foo.pem",
}

在上面的例子中,公共密鑰的名稱( foo )成為關鍵的ID。用於簽署 APEX 的密鑰 ID 寫在 APEX 中。在運行時, apexd使用具有在設備相同ID的公開密鑰驗證的APEX。

郵編簽名

以與簽署 APK 相同的方式簽署 APEX。簽署 APEX 兩次;一次是為小型文件系統( apex_payload.img文件),一旦整個文件。

要在文件級簽訂APEX,將certificate中的這三種方式中的一種屬性:

  • 未設置:如果未設置值時,APEX與位於證書簽名PRODUCT_DEFAULT_DEV_CERTIFICATE 。如果沒有標誌設置,路徑默認為build/target/product/security/testkey
  • <name> :頂點與經簽署的<name>在相同的目錄中的證書PRODUCT_DEFAULT_DEV_CERTIFICATE
  • :<name> :頂點與由名為宋氏模塊中定義的證書簽名的<name> 。證書模塊可以定義如下。
android_app_certificate {
    name: "my_key_name",
    certificate: "dir/cert",
    // this will use dir/cert.x509.pem (the cert) and dir/cert.pk8 (the private key)
}

安裝 APEX

要安裝 APEX,請使用 ADB。

adb install apex_file_name
adb reboot

使用 APEX

重新啟動後,APEX安裝在/apex/<apex_name>@<version>目錄中。可以同時掛載同一個 APEX 的多個版本。其中裝入路徑中,一個對應於最新版本是結合-安裝在/apex/<apex_name>

客戶端可以使用綁定掛載路徑從 APEX 讀取或執行文件。

APEX 通常按如下方式使用:

  1. OEM或ODM預加載一個APEX下/system/apex當裝置被運出。
  2. 在APEX文件通過被訪問/apex/<apex_name>/路徑。
  3. 當APEX的更新版本安裝在/data/apex重新啟動後,路徑指向新的APEX。

使用 APEX 更新服務

要使用 APEX 更新服務:

  1. 將系統分區中的服務標記為可更新。添加選項updatable服務定義。

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. 創建一個新的.rc為更新的服務文件。使用override選項重新定義現有的服務。

    /apex/my.apex@1/etc/init.rc:
    
    service myservice /apex/my.apex@1/bin/myservice
        class core
        user system
        ...
        override
    

服務定義只能在被定義.rc的APEX的文件。 APEX 不支持操作觸發器。

如果標記為可更新的服務在 APEX 激活之前啟動,則啟動將延遲到 APEX 激活完成。

配置系統以支持 APEX 更新

下面的系統屬性設置為true ,以支持APEX文件更新。

<device.mk>:

PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true

BoardConfig.mk:
TARGET_FLATTEN_APEX := false

要不就

<device.mk>:

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

扁平頂點

對於舊設備,更新舊內核以完全支持 APEX 有時是不可能或不可行的。例如,內核可能已建成無CONFIG_BLK_DEV_LOOP=Y ,這是用於安裝APEX內的文件系統映像的關鍵。

Flattened APEX 是一種專門構建的 APEX,可以在具有舊內核的設備上激活。扁平化的 APEX 中的文件直接安裝到內置分區下的目錄中。例如, lib/libFoo.so以扁平APEX my.apex被安裝到/system/apex/my.apex/lib/libFoo.so

激活扁平 APEX 不涉及循環設備。整個目錄/system/apex/my.apex被直接綁定安裝到/apex/name@ver

無法通過從網絡下載 APEX 的更新版本來更新扁平的 APEX,因為無法扁平化下載的 APEX。扁平化的 APEX 只能通過常規 OTA 進行更新。

Flattened APEX 是默認配置。這意味著默認情況下所有 APEX 都是扁平化的,除非您明確配置您的設備以構建非扁平化 APEX 以支持 APEX 更新(如上所述)。

不支持在設備中混合扁平化和非扁平化 APEX。設備中的 APEX 必須全部為非扁平化或全部扁平化。這在為 Mainline 等項目提供預先簽名的 APEX 預構建時尤為重要。未預先簽名(即從源代碼構建)的 APEX 也應該是非扁平化的,並使用正確的密鑰進行簽名。該設備應該從繼承updatable_apex.mk如解釋的更新與APEX服務

壓縮的 APEX

Android 12 及更高版本具有 APEX 壓縮功能,可減少可更新 APEX 包的存儲影響。安裝 APEX 的更新後,雖然不再使用其預安裝的版本,但它仍然佔用相同的空間。該佔用的空間仍然不可用。

APEX壓縮最小化通過使用高壓縮的組APEX上的文件只讀分區(如該存儲衝擊/system分區)。 Android 12 及更高版本使用 DEFLATE zip 壓縮算法。

壓縮不提供以下優化:

  • Bootstrap APEX 需要在引導序列的早期安裝。

  • 不可更新的 APEX。如果安裝在一個APEX的更新版本壓縮不僅有利於/data分區。更新頂點的完整列表,請在模塊化系統組件頁面。

  • 動態共享庫 APEX。由於apexd總是激活這樣的頂點的兩個版本(預安裝和升級),壓縮他們不增加價值。

壓縮的 APEX 文件格式

這是壓縮的 APEX 文件的格式。

Diagram shows the format of a compressed APEX file

圖2.壓縮APEX文件格式

在頂層,壓縮的 APEX 文件是一個 zip 文件,其中包含壓縮級別為 9 的壓縮格式的原始 apex 文件,以及未壓縮存儲的其他文件。

四個文件組成一個 APEX 文件:

  • original_apex :具有9這種壓縮級別癟是原始的,未壓縮的APEX文件
  • apex_manifest.pb :只存儲
  • AndroidManifest.xml :只存儲
  • apex_pubkey :只存儲

apex_manifest.pbAndroidManifest.xml ,和apex_pubkey文件都在其相應的文件的副本original_apex

構建壓縮的 APEX

壓縮APEX可以使用內置apex_compression_tool.py位於工具system/apex/tools

構建系統中提供了幾個與 APEX 壓縮相關的參數。

Android.bp是否APEX文件是壓縮是由控制compressible屬性:

apex {
    name: "apex.test",
    manifest: "apex_manifest.json",
    file_contexts: "file_contexts",
    compressible: true,
}

一個PRODUCT_COMPRESSED_APEX產品標誌控制從源代碼構建的系統映像必須包含壓縮APEX的文件。

對於本地的實驗,你可以通過設置強制構建壓縮頂點OVERRIDE_PRODUCT_COMPRESSED_APEX=true

壓縮文件APEX由生成系統生成具有.capex擴展。該擴展名可以更輕鬆地區分 APEX 文件的壓縮版本和未壓縮版本。

支持的壓縮算法

Android 12 僅支持 deflate-zip 壓縮。

在啟動期間激活壓縮的 APEX 文件

之前壓縮APEX可以激活, original_apex裡面文件的解壓到/data/apex/decompressed目錄。將得到的解壓縮的APEX文件的硬鏈接到/data/apex/active目錄。

考慮以下示例作為上述過程的說明。

考慮/system/apex/com.android.foo.capex作為一種壓縮APEX被激活,具有的versionCode 37。

  1. original_apex內部文件/system/apex/com.android.foo.capex被解壓到/data/apex/decompressed/com.android.foo@37.apex
  2. restorecon /data/apex/decompressed/com.android.foo@37.apex進行,以確認它有一個正確的SELinux的標籤。
  3. 驗證檢查執行/data/apex/decompressed/com.android.foo@37.apex ,以確保其有效性: apexd檢查公鑰捆綁/data/apex/decompressed/com.android.foo@37.apex到驗證它是等於一個捆綁/system/apex/com.android.foo.capex
  4. /data/apex/decompressed/com.android.foo@37.apex文件是硬鏈接到/data/apex/active/com.android.foo@37.apex目錄。
  5. 被執行的未壓縮文件APEX定期激活邏輯/data/apex/active/com.android.foo@37.apex

與OTA互動

壓縮的 APEX 文件對 OTA 交付和應用程序有影響。由於 OTA 更新可能包含一個壓縮的 APEX 文件,其版本級別高於設備上的活動版本,因此在設備重新啟動以應用 OTA 更新之前,必須保留一定數量的可用空間。

為了支持OTA系統, apexd暴露出這兩個粘結劑的API:

  • calculateSizeForCompressedApex -計算的OTA包需要解壓縮APEX文件的大小。這可用於在下載 OTA 之前驗證設備是否有足夠的空間。
  • reserveSpaceForCompressedApex -磁盤以備將來使用的儲備空間apexd用於解OTA包內壓縮APEX的文件。

在一個A / B OTA更新的情況下, apexd嘗試減壓在後台安裝後OTA例程的一部分。如果解壓縮失敗, apexd適用的OTA更新的引導過程中執行解壓縮。

開發 APEX 時考慮的替代方案

以下是 AOSP 在設計 APEX 文件格式時考慮的一些選項,以及包含或排除這些選項的原因。

常規包裹管理系統

Linux發行版的包管理系統等dpkgrpm ,其是強有力的,成熟,和魯棒性。但是,APEX 並未採用它們,因為它們無法在安裝後保護軟件包。僅在安裝軟件包時執行驗證。攻擊者可以在不被注意的情況下破壞已安裝軟件包的完整性。這是對 Android 的回歸,其中所有系統組件都存儲在只讀文件系統中,其完整性受每個 I/O 的 dm-verity 保護。對系統組件的任何篡改都必須被禁止或可被檢測到,以便設備在受到損害時拒絕啟動。

dm-crypt 完整性

在一個APEX容器中的文件是由內置的分區(例如, /system分區)由DM-真實性,其中對文件進行任何修改的分區被安裝後,即使被禁止的保護。為了為文件提供相同級別的安全性,APEX 中的所有文件都存儲在與哈希樹和 vbmeta 描述符配對的文件系統映像中。沒有DM-真實性,頂點在/data分區很容易受到它已經被驗證,安裝後進行的意外修改。

事實上, /data分區也通過加密層,例如DM-隱窩保護。儘管這提供了一定程度的防篡改保護,但其主要目的是隱私,而不是完整性。當攻擊者能夠訪問到/data分區,就沒有進一步的保護,而這又是一個回歸相比每個系統部件中被/system分區。 APEX 文件中的哈希樹與 dm-verity 一起提供了相同級別的內容保護。

將路徑從 /system 重定向到 /apex

包裝在APEX系統組件文件是通過像新的路徑來訪問/apex/<name>/lib/libfoo.so 。當文件被所述部分/system分區,它們是經由路徑訪問諸如/system/lib/libfoo.so 。 APEX 文件(其他 APEX 文件或平台)的客戶端必須使用新路徑。由於路徑更改,您可能需要更新現有代碼。

雖然避免了路徑變化的一種方法是在APEX文件的文件內容覆蓋到/system分區,Android團隊決定不上覆蓋的文件/system分區,因為這可能會影響性能,被疊加的文件數量(甚至可能一個接一個地堆疊)增加。

另一種選擇是劫持文件訪問的功能,如openstatreadlink ,使開頭的路徑/system進行下重定向到其對應的路徑/apex 。 Android 團隊放棄了這個選項,因為改變所有接受路徑的函數是不可行的。例如,一些應用程序靜態鏈接 Bionic,它實現了這些功能。在這種情況下,這些應用程序不會被重定向。