Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

APEX ファイル形式

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

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

背景

Android は、パッケージ インストーラ アプリ(Google Play ストア アプリなど)を使用した、スタンダード アプリモデル(サービスやアクティビティなど)に合致するモジュールの更新に対応していますが、同様のモデルを下位の OS コンポーネントに対して使用する場合は、次のような欠点があります。

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

デザイン

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

APEX のデザインが選択された理由の詳細については、APEX の開発中に検討された代替案をご覧ください。

APEX 形式

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

APEX ファイル形式

図 1. APEX ファイル形式

最上位レベルの APEX ファイルは、圧縮されていない 4 KB 境界にあるファイルを格納する zip ファイルです。

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

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

apex_manifest.json ファイルには、APEX ファイルを識別するパッケージ名とバージョンが含まれます。

APEX ファイルでは、AndroidManifest.xml ファイルを使うことで APK 関連のツールや ADB、PackageManager、Play ストアのようなパッケージ インストーラ アプリなどのインフラストラクチャを使用できます。たとえば、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 マネージャー(または 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 ファイルは APK 署名方式を使用した AndroidManifest.xml ファイルを含む署名付き 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 に依存している場合、2 つの libs が含まれるのは libFoonative_shared_libs プロパティに含まれているときだけです。

複数の ABI の処理

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

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

  • デバイスが 32 ビットのみの場合、32 ビット バリアントのバイナリのみがインストールされます。
  • デバイスが 32 / 64 ABI の両方をサポートしている場合でも、TARGET_PREFER_32_BIT_EXECUTABLES=true のときはバイナリの 32 ビット バリアントのみがインストールされます。
  • デバイスが 64 ビットのみの場合、64 ビット バリアントのバイナリのみがインストールされます。
  • デバイスが 32 / 64 ABI の両方をサポートしている場合でも、TARGET_PREFER_32_BIT_EXECUTABLES=true ではないときは、バイナリの 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 にキーを使って署名します。公開鍵は 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 を検証します。

ZIP 署名

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

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

  • 未設定: 値が設定されていない場合、APEX は PRODUCT_DEFAULT_DEV_CERTIFICATE にある証明書で署名されます。フラグが設定されていない場合、パスはデフォルトで build/target/product/security/testkey です。
  • <name>: APEX は、PRODUCT_DEFAULT_DEV_CERTIFICATE と同じディレクトリにある 証明書で署名されます。
  • :<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 の使用

再起動後、APEX は /apex/<apex_name>@<version> ディレクトリにマウントされます。同じ APEX の複数のバージョンを同時にマウントできます。 マウントパスのうち、最新のバージョンに対応するマウントパスは /apex/<apex_name> にバインド マウントされます。

クライアントは、バインド マウントされたパスを使用して、APEX からファイルを読み取りまたは実行できます。

APEX は通常、次のように使用されます。

  1. OEM または ODM は、デバイスの出荷時に /system/apex にある 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
        

サービス定義は、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 開発時に考慮される代替手段

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 パーティションにオーバーレイすることです。しかし、ファイルを /system パーティションにオーバーレイしないことにしました。オーバーレイされるファイルの数が増えると(段階的に増える場合でも)、パフォーマンスに悪影響が及ぶと考えていたからです。

もう 1 つのオプションは、openstatreadlink などのファイル アクセス機能をハイジャックすることです。これにより、/system で始まるパスは /apex の対応するパスにリダイレクトされます。パスを受け入れるすべての関数を変更することは実際には不可能なため、このオプションは破棄しました。たとえば、一部のアプリでは関数を実装する Bionic を静的にリンクしています。この場合、アプリではリダイレクトは行われません。