| # APEX File Format |
| |
| Android Pony EXpress (APEX) is a container format introduced in Android Q |
| that is used in the install flow for lower-level system |
| modules. This format facilitates the updates of system components that don't fit |
| into the standard Android application model. Some example components are native |
| services and libraries, hardware abstraction layers |
| ([HALs](/https://source.android.com/devices/architecture/hal-types)), runtime |
| ([ART](/https://source.android.com/devices/tech/dalvik)), and class libraries. |
| |
| The term "APEX" can also refer to an APEX file. |
| |
| ## Background |
| |
| Although Android supports updates of modules that fit within the standard app |
| model (for example, services, activities) via package installer apps (such as |
| the Google Play Store app), using a similar model for lower-level OS components |
| has the following drawbacks: |
| |
| - APK-based modules can't be used early in the boot sequence. The package |
| manager is the central repository of information about apps and can only be |
| started from the activity manager, which becomes ready in a later stage of |
| the boot procedure. |
| - The APK format (particularly the manifest) is designed for Android apps and |
| system modules aren't always a good fit. |
| |
| ## Design |
| |
| This section describes the high-level design of the APEX file format and the |
| APEX manager, which is a service that manages APEX files. |
| |
| ### APEX format |
| |
| This is the format of an APEX file. |
| |
|  |
| |
| **Figure 1.** APEX file format |
| |
| At the top level, an APEX file is a zip file in which files are stored |
| uncompressed and located at 4 KB boundaries. |
| |
| The four files in an APEX file are: |
| |
| - `apex_manifest.json` |
| - `AndroidManifest.xml` |
| - `apex_payload.img` |
| - `apex_pubkey` |
| |
| The `apex_manifest.json` file contains the package name and version, which |
| identify an APEX file. |
| |
| The `AndroidManifest.xml` file allows the APEX file to use APK-related tools and |
| infrastructure such as ADB, PackageManager, and package installer apps (such as |
| Play Store). For example, the APEX file can use an existing tool such as `aapt` |
| to inspect basic metadata from the file. The file contains package name and |
| version information. This information is generally also available in |
| `apex_manifest.json`. `AndroidManifest.xml` might contain additional |
| targeting information that can be used by the existing app publishing tools. |
| |
| `apex_manifest.json` is recommended over `AndroidManifest.xml` for new code and |
| systems that deal with APEX. |
| |
| `apex_payload.img` is an ext4 file system image backed by dm-verity. The image |
| is mounted at runtime via a loop device. Specifically, the hash tree and |
| metadata block are created using libavb. The file system payload isn't parsed |
| (because the image should be mountable in place). Regular files are included |
| inside the `apex_payload.img` file. |
| |
| `apex_pubkey` is the public key used to sign the file system image. At runtime, |
| this key ensures that the downloaded APEX is signed with the same entity |
| that signs the same APEX in the built-in partitions. |
| |
| ### APEX manager |
| |
| The APEX manager (or `apexd`) is a native daemon responsible for |
| verifying, installing, and uninstalling APEX files. This process is launched and |
| is ready early in the boot sequence. APEX files are normally pre-installed on |
| the device under `/system/apex`. The APEX manager defaults to using these |
| packages if no updates are available. |
| |
| The update sequence of an APEX uses the |
| [PackageManager class](https://developer.android.com/reference/android/content/pm/PackageManager) |
| and is as follows. |
| |
| 1. An APEX file is downloaded via a package installer app, ADB, or other |
| source. |
| 1. The package manager starts the installation procedure. Upon recognizing that |
| the file is an APEX, the package manager transfers control to the APEX |
| manager. |
| 1. The APEX manager verifies the APEX file. |
| 1. If the APEX file is verified, the internal database of the APEX manager is |
| updated to reflect that the APEX file will be activated at next boot. |
| 1. The requestor of the install receives a broadcast upon successful |
| verification of the package. |
| 1. To continue the installation, the system automatically reboots the device. |
| 1. At reboot, the APEX manager starts, reads the internal database, and does |
| the following for each APEX file listed: |
| |
| 1. Verifies the APEX file. |
| 1. Creates a loop device from the APEX file. |
| 1. Creates a device mapper block device on top of the loop device. |
| 1. Mounts the device mapper block device onto a unique path (for example, |
| <code>/apex/<var>name</var>@<var>ver</var></code>). |
| |
| When all APEX files listed in the internal database are mounted, the APEX |
| manager provides a binder service for other system components to query |
| information about the installed APEX files. For example, the other system |
| components can query the list of APEX files installed in the device or query the |
| exact path where a specific APEX is mounted, so the files can be accessed. |
| |
| ### APEX files are APK files |
| |
| APEX files are valid APK files because they are signed zip archives (using the |
| APK signature scheme) containing an `AndroidManifest.xml` file. This allows APEX |
| files to use the infrastructure for APK files, such as a package installer app, |
| the signing utility, and the package manager. |
| |
| The `AndroidManifest.xml` file inside an APEX file is minimal, consisting of the |
| package `name`, `versionCode`, and optional `targetSdkVersion`, `minSdkVersion`, |
| and `maxSdkVersion` for fine-grained targeting. This information allows APEX |
| files to be delivered via existing channels such as package installer apps and |
| ADB. |
| |
| ### File types supported |
| |
| The APEX format supports these file types: |
| |
| - Native shared libs |
| - Native executables |
| - JAR files |
| - Data files |
| - Config files |
| |
| The APEX format can only update some of these file types. Whether a file |
| type can be updated depends on the platform and how stable the interfaces for |
| the files types are defined. |
| |
| ### Signing |
| |
| APEX files are signed in two ways. First, the `apex_payload.img` (specifically, |
| the vbmeta descriptor appended to `apex_payload.img`) file is signed with a key. |
| Then, the entire APEX is signed using the |
| [APK signature scheme v3](/https://source.android.com/security/apksigning/v3). Two different keys are used |
| in this process. |
| |
| On the device side, a public key corresponding to the private key used to sign |
| the vbmeta descriptor is installed. The APEX manager uses the public key to |
| verify APEXs that are requested to be installed. Each APEX must be signed with |
| different keys and is enforced both at build time and runtime. |
| |
| ### APEX in built-in partitions |
| |
| APEX files can be located in built-in partitions such as `/system`. The |
| partition is |
| already over dm-verity, so the APEX files are mounted directly over the loop |
| device. |
| |
| If an APEX is present in a built-in partition, the APEX can be updated by |
| providing an APEX package with the same package name and a higher version code. |
| The new APEX is stored in `/data` and, similar to APKs, the newer version |
| shadows the version already present in the built-in partition. But unlike APKs, |
| the newer version of the APEX is only activated after reboot. |
| |
| ## Kernel requirements |
| |
| To support APEX mainline modules on an Android device, the following Linux |
| kernel features are required: the loop driver and dm-verity. The loop |
| driver mounts the file system image in an APEX module and dm-verity verifies the |
| APEX module. |
| |
| The performance of the loop driver and dm-verity is important in achieving |
| good system performance when using APEX modules. |
| |
| ### Supported kernel versions |
| |
| APEX mainline modules are supported on devices using kernel versions 4.4 or |
| higher. New devices launching with Android Q or higher |
| must use kernel version 4.9 or higher to support APEX modules. |
| |
| ### Required kernel patches |
| |
| The required kernel patches for supporting APEX modules are included in the |
| Android common tree. To get the patches to support APEX, use the latest version |
| of the Android common tree. |
| |
| #### Kernel version 4.4 |
| |
| This version is only supported for devices that are upgraded from Android 9 to |
| Android Q and want to support APEX modules. To get the |
| required patches, a down-merge from the `android-4.4` branch is strongly |
| recommended. The following is a list of the required individual patches |
| for kernel version 4.4. |
| |
| - UPSTREAM: loop: add ioctl for changing logical block size |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/777013){: .external}) |
| - BACKPORT: block/loop: set hw_sectors |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/777014/7){: .external}) |
| - UPSTREAM: loop: Add LOOP_SET_BLOCK_SIZE in compat ioctl |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/777015/7){: .external}) |
| - ANDROID: mnt: Fix next_descendent |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/405314){: .external}) |
| - ANDROID: mnt: remount should propagate to slaves of slaves |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/320406){: .external}) |
| - ANDROID: mnt: Propagate remount correctly |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/928253){: .external}) |
| - Revert "ANDROID: dm verity: add minimum prefetch size" |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/867875){: .external}) |
| - UPSTREAM: loop: drop caches if offset or block_size are changed |
| ([4.4](https://android-review.googlesource.com/c/kernel/common/+/854265){: .external}) |
| |
| #### Kernel versions 4.9/4.14/4.19 |
| |
| To get the required patches for kernel versions 4.9/4.14/4.19, down-merge from |
| the `android-common` branch. |
| |
| ### Required kernel configuration options |
| |
| The following list shows the base configuration requirements for supporting |
| APEX modules that were introduced in Android Q. The |
| items with an asterisk (\*) are existing requirements from Android 9 and lower. |
| |
| ``` |
| (*) 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 |
| ``` |
| |
| ### Kernel command line parameter requirements |
| |
| To support APEX, make sure the kernel command line parameters meet the following |
| requirements. |
| |
| - `loop.max_loop` must NOT be set |
| - `loop.max_part` must be <= 8 |
| |
| ## Building an APEX |
| |
| Note: Because the implementation details for APEX are still under development, |
| the content in this section is subject to change. |
| |
| This section describes how to build an APEX using the Android build system. |
| The following is an example of `Android.bp` for an APEX named `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` example: |
| |
| ``` |
| { |
| "name": "com.android.example.apex", |
| "version": 1 |
| } |
| ``` |
| |
| `file_contexts` example: |
| |
| ``` |
| (/.*)? u:object_r:system_file:s0 |
| /sub(/.*)? u:object_r:sub_file:s0 |
| /sub/file3 u:object_r:file3_file:s0 |
| ``` |
| |
| #### File types and locations in APEX |
| |
| |File type |Location in APEX | |
| |-------------------|--------------------------------------------------------------| |
| |Shared libraries |`/lib` and `/lib64` (`/lib/arm` for translated arm in x86) | |
| |Executables |`/bin` | |
| |Java libraries |`/javalib` | |
| |Prebuilts |`/etc` | |
| |
| ### Transitive dependencies |
| |
| APEX files automatically include transitive dependencies of native shared libs |
| or executables. For example, if `libFoo` depends on `libBar`, the two libs are |
| included when only `libFoo` is listed in the `native_shared_libs` property. |
| |
| ### Handling multiple ABIs |
| |
| Install the `native_shared_libs` property for both primary and secondary |
| application binary interfaces (ABIs) of the device. If an APEX targets devices |
| with a single ABI (that is, 32 bit only or 64 bit only), only libraries with the |
| corresponding ABI are installed. |
| |
| Install the `binaries` property only for the primary ABI of the device as |
| described below: |
| |
| - If the device is 32 bit only, only the 32-bit variant of the binary is |
| installed. |
| - If the device supports both 32/64 ABIs, but with |
| `TARGET_PREFER_32_BIT_EXECUTABLES=true`, then only the 32-bit variant of the |
| binary is installed. |
| - If the device is 64 bit only, then only the 64-bit variant of the binary is |
| installed. |
| - If the device supports both 32/64 ABIs, but without |
| TARGET_PREFER_32_BIT_EXECUTABLES`=true`, then only the 64-bit variant of the |
| binary is installed. |
| |
| To add fine-grained control over the ABIs of the native libraries and binaries, |
| use the |
| `multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries]` |
| properties. |
| |
| - `first`: Matches the primary ABI of the device. This is the default for |
| binaries. |
| - `lib32`: Matches the 32-bit ABI of the device, if supported. |
| - `lib64`: Matches the 64-bit ABI of the device, it supported. |
| - `prefer32`: Matches the 32-bit ABI of the device, if supported. If the |
| 32-bit ABI isn't supported, matches the 64-bit ABI. |
| - `both`: Matches both ABIs. This is the default for |
| `native_shared_libraries`. |
| |
| The `java`, `libraries`, and `prebuilts` properties are ABI-agnostic. |
| |
| This example is for a device that supports 32/64 and doesn't prefer 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 signing |
| |
| Sign each APEX with different keys. When a new key is required, create a |
| public-private key pair and make an `apex_key` module. Use the `key` property to |
| sign the APEX using the key. The public key is automatically included in the |
| APEX with the name `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", |
| } |
| ``` |
| |
| In the above example, the name of the public key (`foo`) becomes the ID of the |
| key. The ID of the key used to sign an APEX is written in the APEX. At runtime, |
| `apexd` verifies the APEX using a public key with the same ID in the device. |
| |
| ### ZIP signing |
| |
| Sign APEXs in the same way as APKs. Sign APEXs twice, once for the mini file |
| system (`apex_payload.img` file) and once for the entire file. |
| |
| To sign an APEX at the file-level, set the `certificate` property in one of |
| these three ways: |
| |
| - Not set: If no value is set, the APEX is signed with the certificate located |
| at `PRODUCT_DEFAULT_DEV_CERTIFICATE`. If no flag is set, the path defaults |
| to `build/target/product/security/testkey`. |
| - `<name>`: The APEX is signed with the `<name>` certificate in the same |
| directory as `PRODUCT_DEFAULT_DEV_CERTIFICATE`. |
| - `:<name>`: The APEX is signed with the certificate that is defined by the |
| Soong module named `<name>`. The certificate module can be defined as |
| follows. |
| |
| ``` |
| 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) |
| } |
| ``` |
| |
| Note: The `key` and `certificate` values do NOT need to be derived from the same |
| public/private key pairs. APK signing (specified by `certificate`) is required |
| because an APEX is an APK. |
| |
| ## Installing an APEX |
| |
| To install an APEX, use ADB. |
| |
| ``` |
| $ adb install apex_file_name |
| $ adb reboot |
| ``` |
| |
| ## Using an APEX |
| |
| After reboot, the APEX is mounted at the `/apex/<apex_name>@<version>` |
| directory. Multiple versions of the same APEX can be mounted at the same time. |
| Among the mount paths, the one that corresponds to the latest version is |
| bind-mounted at `/apex/<apex_name>`. |
| |
| Clients can use the bind-mounted path to read or execute files from APEX. |
| |
| APEXs are typically used as follows: |
| |
| 1. An OEM or ODM preloads an APEX under `/system/apex` when the device is |
| shipped. |
| 1. Files in the APEX are accessed via the `/apex/<apex_name>/` path. |
| 1. When an updated version of the APEX is installed in `/data/apex`, the path |
| points to the new APEX after reboot. |
| |
| ### Updating a service with an APEX |
| |
| To update a service using an APEX: |
| |
| 1. Mark the service in the system partition as updatable. Add the option |
| `updatable` to the service definition. |
| |
| ``` |
| /system/etc/init/myservice.rc: |
| |
| service myservice /system/bin/myservice |
| class core |
| user system |
| ... |
| updatable |
| ``` |
| |
| 1. Create a new `.rc` file for the updated service. Use the `override` option |
| to redefine the existing service. |
| |
| ``` |
| /apex/my.apex@1/etc/init.rc: |
| |
| service myservice /apex/my.apex@1/bin/myservice |
| class core |
| user system |
| ... |
| override |
| ``` |
| |
| Service definitions can only be defined in the `.rc` file of an APEX. Action |
| triggers aren't supported in APEXs. |
| |
| If a service marked as updatable starts before the APEXs are activated, the |
| start is delayed until the activation of the APEXs is complete. |
| |
| ## Configuring system to support APEX updates |
| |
| Set the following system property to `true` to support APEX file updates. |
| |
| ``` |
| <device.mk>: |
| |
| PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true |
| |
| BoardConfig.mk: |
| TARGET_FLATTEN_APEX := false |
| ``` |
| |
| or just |
| |
| ``` |
| <device.mk>: |
| |
| $(call inherit-product, $(SRC_TARGET_DIR)/product/updatable_apex.mk) |
| ``` |
| |
| ## Flattened APEX |
| |
| For legacy devices, it is sometimes impossible or infeasible to update the old |
| kernel to fully support APEX. For example, the kernel might have been built |
| without `CONFIG_BLK_DEV_LOOP=Y`, which is crucial for mounting the file system |
| image inside an APEX. |
| |
| Flattened APEX is a specially built APEX that can be activated on devices with |
| a legacy kernel. Files in a flattened APEX are directly installed to a directory |
| under the built-in partition. For example, `lib/libFoo.so` in a flattend APEX |
| `my.apex` is installed to `/system/apex/my.apex/lib/libFoo.so`. |
| |
| Activating a flattened APEX doesn't involve the loop device. The entire |
| directory `/system/apex/my.apex` is directly bind-mounted to `/apex/name@ver`. |
| |
| Flattened APEXs can't be updated by downloading updated versions |
| of the APEXs from network because the downloaded APEXs can't be flattened. |
| Flattened APEXs can be updated only via a regular OTA. |
| |
| Note that flattened APEX is the default configuration for now. This means all |
| APEXes are by default flattened unless you explicitly configure your device |
| to support updatable APEX (explained above). |
| |
| Also note that, mixing flattened and non-flattened APEXes in a device is NOT |
| supported. It should be either all non-flattened or all flattened. This is |
| especially important when shipping pre-signed APEX prebuilts for the projects |
| like Mainline. APEXes that are not pre-signed (i.e. built from the source) |
| should also be non-flattened and signed with proper keys in that case. The |
| device should inherit from `updatable_apex.mk` as explained above. |
| |
| ## Alternatives considered when developing APEX |
| |
| Here are some options that we considered when designing the APEX file |
| format, and why we included or excluded them. |
| |
| ### Regular package management systems |
| |
| Linux distributions have package management systems like `dpkg` and `rpm`, |
| which are powerful, mature and robust. However, they weren't |
| adopted for APEX because they can't protect the packages after |
| installation. Verification is done only when packages are being installed. |
| Attackers can break the integrity of the installed packages unnoticed. This is |
| a regression for Android where all system components were stored in read-only |
| file systems whose integrity is protected by dm-verity for every I/O. Any |
| tampering to system components must be prohibited, or be detectable so that |
| the device can refuse to boot if compromised. |
| |
| ### dm-crypt for integrity |
| |
| The files in an APEX container are from built-in partitions (for example, the |
| `/system` partition) that are protected by dm-verity, where any modification to |
| the files are prohibited even after the partitions are mounted. To provide the |
| same level of security to the files, all files in an APEX are stored in a file |
| system image that is paired with a hash tree and a vbmeta descriptor. Without |
| dm-verity, an APEX in the `/data` partition is vulnerable to unintended |
| modifications made after it's verified and installed. |
| |
| In fact, the `/data` partition is also protected by encryption layers such as |
| dm-crypt. Although this provides some level of protection against tampering, its |
| primary purpose is privacy, not integrity. When an attacker gains access to the |
| `/data` partition, there can be no further protection, and this again is a |
| regression compared to every system component being in the `/system` partition. |
| The hash tree inside an APEX file together with dm-verity provides the same |
| level of content protection. |
| |
| ### Redirecting paths from `/system` to `/apex` |
| |
| System component files packaged in an APEX are accessible via new paths like |
| `/apex/<name>/lib/libfoo.so`. When the files were part of the `/system` |
| partition, they were accessible via paths such as `/system/lib/libfoo.so`. A |
| client of an APEX file (other APEX files or the platform) should use the new |
| paths. This change in paths might require updates to the existing code. |
| |
| One way to avoid the path change is to overlay the file contents in an APEX |
| file over the `/system` partition. However, we decided not to overlay files over |
| the `/system` partition because we believed this would negatively affect |
| performance as the number of files being overlayed (possibly even stacked one |
| after another) increases. |
| |
| Another option was to hijack file access functions such as `open`, `stat`, and |
| `readlink`, so that paths that start with `/system` are redirected to their |
| corresponding paths under `/apex`. We discarded this option because it's |
| practically infeasible to change all functions that accept paths. For |
| example, some apps statically link Bionic, which implements the functions. In |
| that case, the redirection won't happen for the app. |