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.
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 and is as follows.
An APEX file is downloaded via a package installer app, ADB, or other source.
The package manager starts the installation procedure. Upon recognizing that the file is an APEX, the package manager transfers control to the APEX manager.
The APEX manager verifies the APEX file.
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.
The requestor of the install receives a broadcast upon successful verification of the package.
To continue the installation, the system automatically reboots the device.
At reboot, the APEX manager starts, reads the internal database, and does the following for each APEX file listed:
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 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.
The APEX format supports these file types:
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.
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. 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 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.
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.
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.
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.
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.
To get the required patches for kernel versions 4.9/4.14/4.19, down-merge from the android-common
branch.
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
To support APEX, make sure the kernel command line parameters meet the following requirements.
loop.max_loop
must NOT be setloop.max_part
must be <= 8Note: 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 type | Location in APEX |
---|---|
Shared libraries | /lib and /lib64 (/lib/arm for translated arm in x86) |
Executables | /bin |
Java libraries | /javalib |
Prebuilts | /etc |
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.
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:
TARGET_PREFER_32_BIT_EXECUTABLES=true
, then only the 32-bit variant of the binary is installed.=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 }, }, }
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.
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:
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.
To install an APEX, use ADB.
$ adb install apex_file_name $ adb reboot
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:
/system/apex
when the device is shipped./apex/<apex_name>/
path./data/apex
, the path points to the new APEX after reboot.To update a service using an APEX:
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
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.
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)
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.
APEX compression is a new feature introduced in Android S. Its main purpose is to reduce the storage impact of updatable APEX packages: after an update to an APEX is installed, its pre-installed version is not used anymore, and space that is taken by it effectively becomes a dead weight.
APEX compression minimizes the storage impact by using a highly-compressed set of APEX files on read-only partitions (e.g. /system
). In Android S a DEFLATE zip compression is used.
Note: compression doesn't provide any optimization in the following scenarios:
kBootstrapApexes
constant in system/apex/apexd/apexd.cpp
./data partition
. Full list of updatable apexes is available at https://source.android.com/devices/architecture/modular-system.apexd
will always activate both versions of such apexes (pre-installed and upgraded), compressing them doesn't provide any value.This is the format of a compressed APEX file.
Figure 2. Compressed APEX file format
At the top level, a compressed APEX file is a zip file containing the original apex in deflated form with compression level of 9 and other files stored uncompressed.
The four files in an APEX file are:
original_apex
: deflated with compression level of 9apex_manifest.pb
: stored onlyAndroidManifest.xml
: stored onlyapex_pubkey
: stored onlyoriginal_apex
is the original uncompressed APEX file.
apex_manifest.pb
AndroidManifest.xml
apex_pubkey
are copies of the corresponding files from original_apex
.
Compressed apex can be built using apex_compression_tool.py
located at system/apex/tools
.
Note: the outer apk container of the produced compressed apex file won't be automatically signed. You will need to manually sign it with using the correct certificate. See Signing Builds for Release.
There are a few different parameters related to APEX compression available in the build system.
In Android.bp
whether an apex is compressible is controlled by compressible
property:
apex { name: "apex.test", manifest: "apex_manifest.json", file_contexts: "file_contexts", compressible: true, }
Note: this only serves as a hint to build system that this apex can be compressed. Such property is required due to the fact that not all apexes are compressible as mentioned in the section above.
TODO(b/183208430): add docs on how this works for prebuilts.
A PRODUCT_COMPRESSED_APEX
product flag is used to control whether a system image built from source should contain compressed apexes or not.
For local experimentation you can force a build to compress apexes by setting OVERRIDE_PRODUCT_COMPRESSED_APEX=true
.
Compressed APEX files generated by the build system will have .capex
extension. It makes it easier to distinguish between compressed and uncompressed versions of an APEX.
Android S only supports deflate zip compression.
Before activating a compressed APEX, original_apex
inside it will be decompressed into /data/apex/decompressed
directory. The resulting decompressed APEX will be hard linked to the /data/apex/active
directory.
Note: because of the hard link step above, it's important that files under /data/apex/decompressed
have the same SELinux label as files under /data/apex/active
.
Consider following example as an illustration of the process described above.
Let's assume that /system/apex/com.android.foo.capex
is a compressed APEX being activated, and it's versionCode
is 37
.
original_apex
inside /system/apex/com.android.foo.capex
is decompressed into /data/apex/decompressed/com.android.foo@37.apex
.restorecon /data/apex/decompressed/com.android.foo@37.apex
is performed to make sure that it has a correct SELinux label./data/apex/decompressed/com.android.foo@37.apex
to ensure it's validity:apexd
checks that public key bundled in /data/apex/decompressed/com.android.foo@37.apex
is equal to the one bundled in /system/apex/com.android.foo.capex
/data/apex/decompressed/com.android.foo@37.apex
is hard linked to /data/apex/active/com.android.foo@37.apex
./data/apex/active/com.android.foo@37.apex
.For more information see implementation of OnStart
function in system/apex/apexd/apexd.cpp
.
Compressed APEX files have some implications on the OTA delivery and application. Since an OTA might contain a compressed APEX file with higher version compared to what is currently active on the device, some free space must be reserved before rebooting a device to apply an OTA.
To help OTA system, two new binder APIs are exposed by apexd:
calculateSizeForCompressedApex
- calculates size required for decompressing APEX files in OTA package. It can be used to check if device has enough space before downloading an OTA.reserveSpaceForCompressedApex
- reserves space on the disk that in the future will be used by apexd for decompression of compressed APEX files inside the OTA package.In case of A/B OTA, apexd
will attempt decompression in the background as part of the postinstall OTA routine. If decompression fails, apexd
will fallback to decompressing during the boot that applies the OTA.
Here are some options that we considered when designing the APEX file format, and why we included or excluded them.
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.
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.
/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.