APEX ファイル形式

Android Pony EXpress(APEX)コンテナ形式は、Android 10 で導入されたもので、低レベルのシステム モジュールのインストール フローで使用されます。この形式を使用すると、標準の Android アプリモデルに適合しないシステム コンポーネントのアップデートが容易になります。対象となるコンポーネントには、ネイティブのサービスやライブラリ、Hardware Abstraction Layer(HAL)、ランタイム(ART)、クラス ライブラリなどがあります。

「APEX」という用語は、APEX ファイルに対しても使用されます。

背景

Android は、パッケージ インストーラ アプリ(Google Play ストア アプリなど)を使用した、標準アプリモデル(サービスやアクティビティなど)に適合するモジュールの更新をサポートしていますが、同様のモデルを低レベルの OS コンポーネントに適用しようとすると、次のような不都合が生じます。

  • APK ベースのモジュールは起動シーケンスの初期には使用できません。パッケージ マネージャーは、アプリに関する情報の中心となるリポジトリで、アクティビティ マネージャーからのみ起動できます。これは起動手順の後半で使用できます。
  • APK 形式(特にマニフェスト)は Android アプリ向けであり、システム モジュールは必ずしも適合しません。

設計

このセクションでは、APEX ファイル形式の概要設計と、APEX ファイルを管理するサービスである APEX マネージャーについて説明します。

APEX にこの設計が採用された理由の詳細については、APEX の開発中に検討された代替案をご覧ください。

APEX 形式

これは APEX ファイルの形式です。

APEX ファイル形式

図 1. APEX ファイル形式

最上位の APEX ファイルは ZIP ファイルであり、その中に保存されるファイルは非圧縮で、4 KB 境界に配置されます。

APEX ファイルに含まれる 4 つのファイルは次のとおりです。

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

apex_manifest.json ファイルには、APEX ファイルを識別するパッケージ名とバージョンが含まれます。これは JSON 形式の ApexManifest プロトコル バッファです。

AndroidManifest.xml ファイルを使うことで、APEX ファイルは ADB、PackageManager、パッケージ インストーラ アプリ(Google Play ストアなど)のような APK 関連ツールやインフラストラクチャを利用できます。たとえば、APEX ファイルは aapt などの既存のツールを利用して、ファイルの基本的なメタデータを検査できます。このファイルには、パッケージ名とバージョン情報が含まれています。この情報は通常、apex_manifest.json でも参照できます。

APEX を扱う新しいコードとシステムに対しては AndroidManifest.xml よりも apex_manifest.json をおすすめします。AndroidManifest.xml には既存のアプリ公開ツールに使用されるその他のターゲティング情報が含まれている可能性があります。

apex_payload.img は dm-verity で保護された ext4 ファイル システム イメージです。このイメージは、ループバック デバイスを介してランタイムにマウントされます。具体的には、libavb ライブラリを使用してハッシュツリーとメタデータ ブロックが作成されます。ファイル システムのペイロードは解析されません(イメージをマウントするだけで動作するため)。通常のファイルは apex_payload.img ファイル内に含まれます。

apex_pubkey は、ファイル システム イメージへの署名に使用される公開鍵です。ランタイムにはこの鍵を使用して、ダウンロードした APEX が組み込みパーティション内の APEX と同じエンティティにより署名されていることを確認します。

APEX 命名ガイドライン

プラットフォームの進展に伴う新しい APEX 間の名称の競合を避けるために、次の命名ガイドラインを使用します。

  • com.android.*
    • AOSP APEX 用に予約されています。会社やデバイスに固有ではありません。
  • com.<companyname>.*
    • 会社用に予約されています。その会社の複数のデバイスで使用される可能性があります。
  • com.<companyname>.<devicename>.*
    • 特定のデバイス(またはデバイスのサブセット)に固有の APEX 用に予約されています。

APEX マネージャー

APEX マネージャー(または apexd)は、APEX ファイルの検証、インストール、アンインストールを行うスタンドアロンのネイティブ プロセスです。このプロセスは起動シーケンスの早い段階で開始され、準備が整います。通常、APEX ファイルはデバイスの /system/apex の下にプリインストールされています。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 マネージャーは他のシステム コンポーネントにバインダー サービスを提供して、インストールされた APEX ファイルに関する情報をクエリします。たとえば、他のシステム コンポーネントは、デバイスにインストールされている APEX ファイルのリストや、特定の APEX がマウントされているパスをクエリできるため、ファイルにアクセスが可能です。

APK ファイルである APEX ファイル

APEX ファイルは、AndroidManifest.xml ファイルを含み、APK 署名スキームの署名が付いた ZIP アーカイブであるため、有効な APK ファイルです。したがって、APEX ファイルは、パッケージ インストーラ アプリ、署名ユーティリティ、パッケージ マネージャーなどの APK ファイルのインフラストラクチャを利用できます。

APEX ファイル内の AndroidManifest.xml ファイルは最小限のもので、パッケージの nameversionCode、および詳細なターゲティングの必要性に応じて targetSdkVersionminSdkVersionmaxSdkVersion を含んでいます。この情報により、パッケージ インストーラ アプリや ADB などの既存のチャネルを介して APEX ファイルを配信できます。

サポートされるファイル形式

APEX 形式でサポートされるファイル形式は次のとおりです。

  • ネイティブ共有ライブラリ
  • ネイティブ実行可能ファイル
  • JAR ファイル
  • データファイル
  • 構成ファイル

APEX がこれらのファイル形式をすべて更新できるとは限りません。ファイル形式を更新できるかどうかは、プラットフォームと、そのファイル形式のインターフェース定義の安定性によって決まります。

署名オプション

APEX ファイルは 2 つの方法で署名されます。まず、apex_payload.img(厳密には、apex_payload.img に追加される vbmeta 記述子)ファイルが鍵で署名されます。 次に、APEX 全体が APK 署名スキーム v3 を使用して署名されます。このプロセスでは 2 つの異なる鍵が使用されます。

デバイス側には、vbmeta 記述子への署名に使用された秘密鍵に対応する公開鍵がインストールされます。APEX マネージャーは、公開鍵を使用して、インストールがリクエストされている APEX を検証します。各 APEX は、異なる鍵で署名する必要があり、ビルド時と実行時の両方で必要です。

組み込みパーティション内の APEX

APEX ファイルは、/system のような組み込みパーティションに置くことができます。パーティションはすでに dm-verity で保護済みのため、APEX ファイルはループバック デバイスにそのままマウントされます。

APEX が組み込みパーティションに存在する場合、APEX パッケージに同じパッケージ名と、同等以上のバージョンのコードを指定することで APEX を更新できます。新しい APEX は /data に格納され、APK と同様に、新たにインストールされたバージョンは組み込みパーティションにすでに存在するバージョンをシャドウします。しかし、APK とは異なり、APEX の新たにインストールされたバージョンは再起動後にのみ有効になります。

カーネルの要件

Android デバイスで APEX メインライン モジュールをサポートするには、ループバック ドライバと dm-verity という Linux カーネル機能が必要です。ループバック ドライバは APEX モジュールのファイル システム イメージをマウントし、dm-verity は APEX モジュールを検証します。

ループバック ドライバと dm-verity のパフォーマンスは、APEX モジュールを使用する際のシステム パフォーマンスを適切なものにするうえで重要です。

サポートされているカーネル バージョン

APEX メインライン モジュールは、カーネル バージョン 4.4 以降を使用するデバイスでサポートされています。Android 10 以降を搭載してリリースされる新しいデバイスで APEX モジュールをサポートするためには、カーネル バージョン 4.9 以降を使用する必要があります。

必要なカーネルパッチ

APEX モジュールをサポートするために必要なカーネルパッチは、Android 共通ツリーに含まれています。APEX をサポートするパッチを入手するには、Android 共通ツリーの最新バージョンを使用してください。

カーネル バージョン 4.4

このバージョンは、Android 9 から Android 10 にアップグレードしたデバイスで APEX モジュールをサポートする場合にのみ使用できます。必要なパッチを取得するには、android-4.4 ブランチからのダウンマージを強くおすすめします。カーネル バージョン 4.4 に必要な個別パッチは次のとおりです。

  • UPSTREAM: loop: add ioctl for changing logical block size(4.4
  • BACKPORT: block/loop: set hw_sectors(4.4
  • UPSTREAM: loop: Add LOOP_SET_BLOCK_SIZE in compat ioctl(4.4
  • ANDROID: mnt: Fix next_descendent(4.4
  • ANDROID: mnt: remount should propagate to slaves of slaves(4.4
  • ANDROID: mnt: Propagate remount correctly(4.4
  • Revert "ANDROID: dm verity: add minimum prefetch size"(4.4
  • UPSTREAM: loop: drop caches if offset or block_size are changed(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 をビルドする方法について説明します。 以下は、apex.test という名前の APEX 用の Android.bp の例です。

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(x86 に変換された ARM 用は /lib/arm
実行可能ファイル /bin
Java ライブラリ /javalib
事前ビルド /etc

推移的依存関係

APEX ファイルには、ネイティブ共有ライブラリや実行可能ファイルの推移的依存関係が自動的に含まれます。たとえば、libFoolibBar に依存している場合、native_shared_libs プロパティにリストされているのが libFoo のみであっても、両方のライブラリが含まれます。

複数の ABI を処理する

デバイスのプライマリとセカンダリ両方のアプリケーション バイナリ インターフェース(ABI)に native_shared_libs プロパティをインストールします。APEX が 1 つの ABI(32 ビットのみ、または 64 ビットのみ)を有するデバイスを対象としている場合、対応する ABI を含むライブラリのみがインストールされます。

デバイスのプライマリ ABI にのみ、次のように binaries プロパティをインストールします。

  • デバイスが 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 モジュールを作成します。その鍵で APEX に署名するには、key プロパティを使用します。公開鍵は avb_pubkey という名前で APEX に自動的に含まれます。

# 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 を検証します。

APEX 署名

APK に署名するのと同じ方法で APEX に署名します。APEX には 2 回署名します(ミニ ファイル システム(apex_payload.img ファイル)に対して 1 回と、ファイル全体に対して 1 回)。

ファイルレベルで APEX に署名するには、certificate プロパティを次の 3 つの方法のいずれかに設定します。

  • 未設定: 値が設定されていない場合、APEX は PRODUCT_DEFAULT_DEV_CERTIFICATE にある証明書で署名されます。フラグが設定されていない場合、パスはデフォルトで build/target/product/security/testkey です。
  • <name>: APEX は、PRODUCT_DEFAULT_DEV_CERTIFICATE と同じディレクトリにある <name> 証明書で署名されます。
  • :<name>: APEX は、<name> という名前の Soong モジュールで定義された証明書で署名されます。証明書モジュールは次のように定義できます。
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_manifest.jsonsupportsRebootlessUpdatetrue に設定されていて、現在インストールされている APEX が使用されていない場合(例: 含まれているサービスが停止している場合)、--force-non-staged フラグで再起動することなく新しい APEX をインストールできます。

adb install --force-non-staged apex_file_name

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/etc/init.rc:
    
    service myservice /apex/my.apex/bin/myservice
        class core
        user system
        ...
        override
    

サービス定義は、APEX の .rc ファイルでのみ定義できます。APEX ではアクション トリガーはサポートされていません。

更新可能としてマークされたサービスが、APEX が有効になる前に開始されると、APEX の有効化が完了するまで開始が遅延します。

APEX アップデートをサポートするシステムを設定する

APEX ファイルのアップデートをサポートするには、次のようにシステム プロパティを true に設定します。

<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

以前のデバイスでは、古いカーネルを更新して APEX を完全にサポートすることが原理的または現実的に不可能な場合があります。たとえば、CONFIG_BLK_DEV_LOOP=Y の指定なしにビルドされたカーネルもありますが、その場合は APEX 内のイメージ ファイル システムをマウントできません。

フラット化 APEX は、古いカーネルのデバイスでも有効化できるように特別にビルドされた APEX です。フラット化 APEX 内のファイルは、組み込みパーティションの下のディレクトリにそのままインストールされます。たとえば、フラット化 APEX である my.apex 内の lib/libFoo.so/system/apex/my.apex/lib/libFoo.so にインストールされます。

フラット化 APEX の有効化には、ループデバイスは必要ありません。/system/apex/my.apex ディレクトリ全体がそのまま /apex/name@ver にバインド マウントされます。

フラット化 APEX は、APEX の更新版をネットワークからダウンロードして更新することはできません。ダウンロードした APEX はフラット化できないためです。フラット化 APEX は、通常の OTA を介してのみ更新できます。

フラット化 APEX がデフォルト設定です。つまり、APEX アップデートをサポートするため非フラット化 APEX をビルドするよう明示的にデバイスを設定(方法は上述のとおり)しない限り、デフォルトでは APEX はすべてフラット化されます。

1 つのデバイスにフラット化 APEX と非フラット化 APEX を混在させることはサポートされていません。1 つのデバイスの APEX は、すべて非フラット化またはすべてフラット化されている必要があります。 これは、Mainline などのプロジェクト向けにビルド済みで署名済みの APEX を出荷する場合に特に重要です。ソースからビルドされ、事前に署名されていない APEX も、フラット化せずに適切な鍵を使用して署名する必要があります。デバイスは、APEX によるサービスの更新で説明されているように updatable_apex.mk から継承されます。

圧縮 APEX

Android 12 以降では、更新可能な APEX パッケージによる保存容量への影響を軽減する APEX 圧縮がサポートされています。APEX のアップデートをインストールすると、プリインストールされたバージョンは使用されなくなりますが、占有する容量は同じです。その占有スペースは利用できなくなります。

APEX 圧縮は、読み取り専用パーティション(/system パーティションなど)で高度に圧縮された APEX ファイルを使用することで、このような保存容量への影響を最小限に抑えます。Android 12 以降では、DEFLATE zip 圧縮アルゴリズムが使用されます。

圧縮では、以下に対する最適化は行われません。

  • 起動シーケンスの非常に早い段階でマウントする必要があるブートストラップ APEX。

  • 更新不可能な APEX。圧縮は、APEX の更新版が /data パーティションにインストールされている場合にのみ意味があります。更新可能な APEX の一覧については、モジュラー システム コンポーネントをご覧ください。

  • 動的共有ライブラリの APEX。apexd により常にそのような APEX の両方のバージョン(プリインストールされているバージョンとアップグレードされたバージョン)が有効にされるため、圧縮しても意味がありません。

圧縮 APEX ファイルの形式

圧縮 APEX ファイルの形式は次のとおりです。

圧縮 APEX ファイルの形式を示す図

図 2. 圧縮 APEX ファイルの形式

圧縮 APEX ファイルは、最上位レベルでは、圧縮レベル 9、デフレート形式の元の apex ファイルと、非圧縮状態で保存された他のファイルからなる zip ファイルです。

APEX ファイルは、次の 4 つのファイルで構成されます。

  • original_apex: 圧縮レベル 9 のデフレート形式。これは元の非圧縮 APEX ファイルです。
  • apex_manifest.pb: そのまま保存
  • AndroidManifest.xml: そのまま保存
  • apex_pubkey: そのまま保存

apex_manifest.pbAndroidManifest.xmlapex_pubkey ファイルは、original_apex 内の対応するファイルのコピーとなっています。

圧縮 APEX をビルドする

圧縮 APEX は、system/apex/tools にある apex_compression_tool.py ツールでビルドできます。

ビルドシステムには 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 を圧縮するようにできます。

ビルドシステムによって生成された圧縮 APEX ファイルには、.capex という拡張子があります。この拡張子により、APEX ファイルの圧縮版と非圧縮版を簡単に区別できます。

サポートされている圧縮アルゴリズム

Android 12 では、deflate-zip 圧縮のみがサポートされています。

起動時に圧縮 APEX ファイルを有効にする

圧縮 APEX を有効にする前に、非圧縮版の中の original_apex ファイルを圧縮解除して /data/apex/decompressed ディレクトリに展開します。圧縮解除された APEX ファイルは /data/apex/active ディレクトリにハードリンクされます。

以上の手順の例として、次をご覧ください。

/system/apex/com.android.foo.capex は、versionCode が 37 で、圧縮 APEX が有効になっているとします。

  1. /system/apex/com.android.foo.capex 内の original_apex ファイルは、圧縮解除されて /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 は以下の 2 つのバインダ API を公開しています。

  • calculateSizeForCompressedApex - OTA パッケージ内の APEX ファイルを圧縮解除するために必要なサイズを計算します。これを使用して、OTA をダウンロードする前にデバイスに空き容量が十分あるかを確認できます。
  • reserveSpaceForCompressedApex - apexd で OTA パッケージ内の圧縮 APEX ファイルを圧縮解除する際に使用するディスク上のスペースを予約します。

A/B OTA アップデートの場合、apexd によりインストール後の OTA ルーティンの一環としてバックグラウンドで圧縮解除が試みられます。圧縮解除が失敗した場合、OTA アップデートを適用する起動の間に、apexd により圧縮解除が実行されます。

APEX の開発中に検討された代替案

AOSP が APEX ファイル形式を設計する際に検討した選択肢と、その取捨選択の理由を以下に説明します。

通常のパッケージ管理システム

Linux ディストリビューションには dpkgrpm のような、強力かつ堅牢で成熟したパッケージ管理システムがあります。しかし、インストール後にパッケージを保護できないため、APEX には採用されませんでした。検証が行われるのはパッケージのインストール時のみのため、その後気づかぬうちに攻撃者によりパッケージの完全性が損なわれる恐れがあります。これは、すべてのシステム コンポーネントが読み取り専用ファイル システムに格納され、すべての I/O に対して dm-verity によりその完全性が保護される Android にとっては回帰です。いかなるシステム コンポーネントの改ざんも禁止するか、検出可能にして、侵害された場合はデバイスの起動を拒否できるようにする必要があります。

完全性のための dm-crypt

APEX コンテナ内のファイルは、dm-verity で保護される組み込みパーティション(たとえば、/system パーティションなど)からのもので、パーティションのマウント後でもファイルの変更はできません。同じレベルのセキュリティをファイルに提供するために、APEX 内のすべてのファイルは、ハッシュツリーおよび vbmeta 記述子とペアになったファイル システム イメージに格納されます。dm-verity がないと、/data パーティション内の APEX は、検証およびインストール後に意図しない変更が行われる可能性があります。

実際、/data パーティションは dm-crypt などの暗号化レイヤでも保護されます。これは改ざんに対してある程度保護を行いますが、主な目的はプライバシーであり、完全性ではありません。攻撃者が /data パーティションへのアクセス権を獲得した場合は、それ以上の保護はできません。これも、/system パーティションにすべてのシステム コンポーネントを置くことに対する回帰です。 APEX ファイル内のハッシュツリーと dm-verity を組み合わせることで、同じレベルのコンテンツ保護を実現しています。

/system から /apex にパスをリダイレクトする

APEX でパッケージ化されたシステム コンポーネント ファイルは、/apex/<name>/lib/libfoo.so のような新しいパスを介してアクセスできます。ファイルが /system パーティションの一部であったときは、/system/lib/libfoo.so のようなパスでアクセスできました。APEX ファイルのクライアント(他の APEX ファイルやプラットフォーム)は、新しいパスを使用する必要があります。パスの変更の結果、既存のコードを更新しなければならない場合があります。

パスの変更を回避する方法の 1 つは、APEX ファイル内のファイル コンテンツを /system パーティションにオーバーレイすることです。しかし、Android チームは、/system パーティションにファイルをオーバーレイしないという決定をしました。オーバーレイするファイルの数が増えると(1 つずつ増えるとしても)、パフォーマンスに影響する可能性があるためです。

もう 1 つの選択肢は、openstatreadlink などのファイル アクセス機能をハイジャックして、/system で始まるパスが /apex 下の対応するパスにリダイレクトされるようにすることでした。パスを受け取る関数をすべて変更するのは現実的でないため、Android チームはこの選択肢を放棄しました。たとえば、一部のアプリでは関数を実装する Bionic を静的にリンクしています。このような場合、そのようなアプリにはリダイレクトされません。