APEX-Dateiformat

Das APEX-Containerformat (Android Pony EXpress) wurde in Android 10 eingeführt und wird im Installationsablauf für Systemmodule auf niedrigerer Ebene verwendet. Dieses Format erleichtert die Aktualisierung von Systemkomponenten, die nicht in das Standardmodell für Android-Anwendungen passen. Beispiele für Komponenten sind native Dienste und Bibliotheken, Hardware-Abstraktionsebenen (HALs), die Laufzeitumgebung (ART) und Klassenbibliotheken.

Der Begriff „APEX“ kann sich auch auf eine APEX-Datei beziehen.

Hintergrund

Android unterstützt zwar Updates von Modulen, die in das Standard-App-Modell passen (z. B. Dienste, Aktivitäten) über Paketinstallations-Apps (z. B. die Google Play Store App), die Verwendung eines ähnlichen Modells für Betriebssystemkomponenten auf niedrigerer Ebene hat jedoch die folgenden Nachteile:

  • APK-basierte Module können nicht früh im Bootvorgang verwendet werden. Der Paketmanager ist das zentrale Informations-Repository für Apps und kann nur über den Aktivitätsmanager gestartet werden, der in einer späteren Phase des Bootvorgangs bereit ist.
  • Das APK-Format (insbesondere das Manifest) ist für Android-Apps konzipiert und eignet sich nicht immer für Systemmodule.

Design

In diesem Abschnitt wird das allgemeine Design des APEX-Dateiformats und des APEX-Managers beschrieben, einem Dienst, der APEX-Dateien verwaltet.

Weitere Informationen dazu, warum dieses Design für APEX ausgewählt wurde, finden Sie unter Alternativen, die bei der Entwicklung von APEX in Betracht gezogen wurden.

APEX-Format

Dies ist das Format einer APEX-Datei.

APEX-Dateiformat

Abbildung 1: APEX-Dateiformat

Auf der obersten Ebene ist eine APEX-Datei eine ZIP-Datei, in der Dateien unkomprimiert und an 4‑KB-Grenzen gespeichert werden.

Die vier Dateien in einer APEX-Datei sind:

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

Die Datei apex_manifest.json enthält den Paketnamen und die Version, die eine APEX-Datei identifizieren. Dies ist ein ApexManifest-Protokollpuffer im JSON-Format.

Mit der Datei AndroidManifest.xml kann die APEX-Datei APK-bezogene Tools und Infrastruktur wie ADB, PackageManager und Paketinstallations-Apps (z. B. Play Store) verwenden. Für die APEX-Datei kann beispielsweise ein vorhandenes Tool wie aapt verwendet werden, um grundlegende Metadaten aus der Datei zu prüfen. Die Datei enthält den Paketnamen und die Versionsinformationen. Diese Informationen sind in der Regel auch in apex_manifest.json verfügbar.

apex_manifest.json wird für neuen Code und Systeme, die APEX verwenden, gegenüber AndroidManifest.xml empfohlen. AndroidManifest.xml kann zusätzliche Targeting-Informationen enthalten, die von den vorhandenen App-Veröffentlichungstools verwendet werden können.

apex_payload.img ist ein ext4-Dateisystem-Image, das von dm-verity unterstützt wird. Das Image wird zur Laufzeit über ein Loopback-Gerät eingebunden. Hash-Baum und Metadatenblock werden mit der libavb-Bibliothek erstellt. Die Nutzlast des Dateisystems wird nicht geparst, da das Image direkt gemountet werden soll. Reguläre Dateien sind in der Datei apex_payload.img enthalten.

apex_pubkey ist der öffentliche Schlüssel, der zum Signieren des Dateisystem-Images verwendet wird. Zur Laufzeit sorgt dieser Schlüssel dafür, dass das heruntergeladene APEX mit derselben Identität signiert wird, mit der dasselbe APEX in den integrierten Partitionen signiert wird.

APEX-Benennungsrichtlinien

Um Namenskonflikte zwischen neuen APEX-Modulen zu vermeiden, wenn sich die Plattform weiterentwickelt, sollten Sie die folgenden Namensrichtlinien beachten:

  • com.android.*
    • Für AOSP-APEX-Module reserviert. Nicht einzigartig für ein Unternehmen oder Gerät.
  • com.<companyname>.*
    • Für ein Unternehmen reserviert. Wird möglicherweise von mehreren Geräten dieses Unternehmens verwendet.
  • com.<companyname>.<devicename>.*
    • Für APEX-Dateien reserviert, die für ein bestimmtes Gerät (oder eine bestimmte Teilmenge von Geräten) eindeutig sind.

APEX-Manager

Der APEX-Manager (oder apexd) ist ein eigenständiger nativer Prozess, der für das Überprüfen, Installieren und Deinstallieren von APEX-Dateien zuständig ist. Dieser Prozess wird gestartet und ist früh im Bootvorgang bereit. APEX-Dateien sind normalerweise auf dem Gerät unter /system/apex vorinstalliert. Der APEX-Manager verwendet standardmäßig diese Pakete, wenn keine Updates verfügbar sind.

Die Aktualisierungssequenz eines APEX verwendet die PackageManager-Klasse und sieht so aus:

  1. Eine APEX-Datei wird über eine Paketinstallations-App, ADB oder eine andere Quelle heruntergeladen.
  2. Der Paketmanager startet die Installation. Wenn der Paketmanager erkennt, dass es sich bei der Datei um ein APEX handelt, übergibt er die Steuerung an den APEX-Manager.
  3. Der APEX-Manager überprüft die APEX-Datei.
  4. Wenn die APEX-Datei überprüft wird, wird die interne Datenbank des APEX-Managers aktualisiert, um widerzuspiegeln, dass die APEX-Datei beim nächsten Start aktiviert wird.
  5. Der Anforderer der Installation erhält nach erfolgreicher Paketüberprüfung eine Broadcast-Nachricht.
  6. Damit die Installation fortgesetzt werden kann, muss das System neu gestartet werden.
  7. Beim nächsten Start wird der APEX-Manager gestartet, die interne Datenbank gelesen und für jede aufgeführte APEX-Datei wird Folgendes ausgeführt:

    1. Überprüft die APEX-Datei.
    2. Erstellt ein Loopback-Gerät aus der APEX-Datei.
    3. Erstellt ein Device Mapper-Blockgerät auf dem Loopback-Gerät.
    4. Das Device Mapper-Blockgerät wird auf einem eindeutigen Pfad gemountet (z. B. /apex/name@ver).

Wenn alle in der internen Datenbank aufgeführten APEX-Dateien eingebunden sind, stellt der APEX-Manager einen Binder-Dienst für andere Systemkomponenten bereit, um Informationen zu den installierten APEX-Dateien abzufragen. Die anderen Systemkomponenten können beispielsweise die Liste der auf dem Gerät installierten APEX-Dateien oder den genauen Pfad abfragen, in dem ein bestimmter APEX bereitgestellt wird, damit auf die Dateien zugegriffen werden kann.

APEX-Dateien sind APK-Dateien

APEX-Dateien sind gültige APK-Dateien, da sie signierte ZIP-Archive (mit dem APK-Signaturschema) mit einer AndroidManifest.xml-Datei sind. So können APEX-Dateien die Infrastruktur für APK-Dateien nutzen, z. B. eine Paketinstallations-App, das Signaturdienstprogramm und den Paketmanager.

Die Datei AndroidManifest.xml in einer APEX-Datei ist minimal und besteht aus dem Paket name, versionCode und optional targetSdkVersion, minSdkVersion und maxSdkVersion für das detaillierte Targeting. Diese Informationen ermöglichen die Bereitstellung von APEX-Dateien über vorhandene Kanäle wie Paketinstallations-Apps und ADB.

Unterstützte Dateitypen

Das APEX-Format unterstützt die folgenden Dateitypen:

  • Gemeinsam genutzte native Bibliotheken
  • Native ausführbare Dateien
  • JAR-Dateien
  • Datendateien
  • Konfigurationsdateien

Das bedeutet nicht, dass APEX alle diese Dateitypen aktualisieren kann. Ob ein Dateityp aktualisiert werden kann, hängt von der Plattform und davon ab, wie stabil die Definitionen der Schnittstellen für die Dateitypen sind.

Signaturoptionen

APEX-Dateien werden auf zwei Arten signiert. Zuerst wird die Datei apex_payload.img (insbesondere der an apex_payload.img angehängte vbmeta-Deskriptor) mit einem Schlüssel signiert. Anschließend wird das gesamte APEX mit dem APK-Signaturschema v3 signiert. Bei diesem Vorgang werden zwei verschiedene Schlüssel verwendet.

Auf der Geräteseite ist ein öffentlicher Schlüssel installiert, der dem privaten Schlüssel entspricht, der zum Signieren des vbmeta-Deskriptors verwendet wurde. Der APEX-Manager verwendet den öffentlichen Schlüssel, um APEX-Dateien zu überprüfen, die installiert werden sollen. Jedes APEX muss mit unterschiedlichen Schlüsseln signiert werden. Dies wird sowohl zur Build-Zeit als auch zur Laufzeit erzwungen.

APEX in integrierten Partitionen

APEX-Dateien können sich in integrierten Partitionen wie /system befinden. Die Partition ist bereits über dm-verity gesichert, sodass die APEX-Dateien direkt über das Loopback-Gerät bereitgestellt werden.

Wenn ein APEX in einer integrierten Partition vorhanden ist, kann er aktualisiert werden, indem ein APEX-Paket mit demselben Paketnamen und einem Versionscode bereitgestellt wird, der größer oder gleich ist. Das neue APEX wird in /data gespeichert. Ähnlich wie bei APKs wird die bereits in der integrierten Partition vorhandene Version durch die neu installierte Version überschrieben. Im Gegensatz zu APKs wird die neu installierte Version des APEX jedoch erst nach dem Neustart aktiviert.

Kernel-Anforderungen

Damit APEX-Mainline-Module auf einem Android-Gerät unterstützt werden, sind die folgenden Linux-Kernel-Funktionen erforderlich: der Loopback-Treiber und dm-verity. Der Loopback-Treiber hängt das Dateisystem-Image in ein APEX-Modul ein und dm-verity prüft das APEX-Modul.

Die Leistung des Loopback-Treibers und von dm-verity ist wichtig, um bei der Verwendung von APEX-Modulen eine gute Systemleistung zu erzielen.

Unterstützte Kernel-Versionen

APEX-Hauptmodule werden auf Geräten mit Kernel-Version 4.4 oder höher unterstützt. Auf neuen Geräten, die bei Markteinführung Android 10 oder höher nutzen, muss die Kernelversion 4.9 oder höher verwendet werden, um APEX-Module zu unterstützen.

Erforderliche Kernel-Patches

Die erforderlichen Kernel-Patches zur Unterstützung von APEX-Modulen sind im gemeinsamen Android-Baum enthalten. Wenn Sie die Patches zur Unterstützung von APEX erhalten möchten, verwenden Sie die aktuelle Version des gemeinsamen Android-Baums.

Kernel-Version 4.4

Diese Version wird nur für Geräte unterstützt, die von Android 9 auf Android 10 aktualisiert wurden und APEX-Module unterstützen sollen. Um die erforderlichen Patches zu erhalten, wird ein Down-Merge vom android-4.4-Branch dringend empfohlen. Im Folgenden finden Sie eine Liste der erforderlichen einzelnen Patches für die Kernelversion 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)
  • „ANDROID: dm verity: add minimum prefetch size“ zurücksetzen (4.4)
  • UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)

Kernel-Versionen 4.9/4.14/4.19

Die erforderlichen Patches für die Kernelversionen 4.9, 4.14 und 4.19 können aus dem Branch android-common heruntergemergt werden.

Erforderliche Kernelkonfigurationsoptionen

In der folgenden Liste sind die Anforderungen an die Basiskonfiguration für die Unterstützung von APEX-Modulen aufgeführt, die in Android 10 eingeführt wurden. Die mit einem Sternchen (*) gekennzeichneten Elemente sind bestehende Anforderungen aus Android 9 und niedrigeren Versionen.

(*) 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

Anforderungen an Kernel-Befehlszeilenparameter

Damit APEX unterstützt wird, müssen die Kernel-Befehlszeilenparameter die folgenden Anforderungen erfüllen:

  • loop.max_loop darf NICHT festgelegt werden
  • loop.max_part muss <= 8 sein

APEX erstellen

In diesem Abschnitt wird beschrieben, wie Sie mit dem Android-Build-System ein APEX erstellen. Das folgende Beispiel zeigt Android.bp für ein APEX mit dem Namen 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-Beispiel:

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

file_contexts-Beispiel:

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

Dateitypen und Speicherorte in APEX

Dateityp Speicherort in APEX
Geteilte Fotogalerien /lib und /lib64 (/lib/arm für übersetzte Arm-Architektur in x86)
Ausführbare Dateien /bin
Java-Bibliotheken /javalib
Vorgefertigte /etc

Transitive Abhängigkeiten

APEX-Dateien enthalten automatisch transitive Abhängigkeiten von nativen freigegebenen Bibliotheken oder ausführbaren Dateien. Wenn libFoo beispielsweise von libBar abhängt, werden beide Bibliotheken einbezogen, wenn nur libFoo in der native_shared_libs-Property aufgeführt ist.

Mehrere ABIs verarbeiten

Installieren Sie das Attribut native_shared_libs für die primären und sekundären Binärschnittstellen (Application Binary Interfaces, ABIs) des Geräts. Wenn ein APEX auf Geräte mit einem einzelnen ABI (d. h. nur 32 Bit oder nur 64 Bit) ausgerichtet ist, werden nur Bibliotheken mit dem entsprechenden ABI installiert.

Installieren Sie die binaries-Property nur für die primäre ABI des Geräts, wie unten beschrieben:

  • Wenn das Gerät nur 32 Bit unterstützt, wird nur die 32-Bit-Variante des Binärprogramms installiert.
  • Wenn das Gerät nur 64 Bit unterstützt, wird nur die 64-Bit-Variante der Binärdatei installiert.

Wenn Sie die ABIs der nativen Bibliotheken und Binärdateien genauer steuern möchten, verwenden Sie die multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries]-Attribute.

  • first: Entspricht dem primären ABI des Geräts. Dies ist die Standardeinstellung für Binärdateien.
  • lib32: Entspricht dem 32-Bit-ABI des Geräts, sofern unterstützt.
  • lib64: Entspricht der 64-Bit-ABI des Geräts, sofern es unterstützt wird.
  • prefer32: Entspricht dem 32-Bit-ABI des Geräts, sofern unterstützt. Wenn das 32-Bit-ABI nicht unterstützt wird, entspricht es dem 64-Bit-ABI.
  • both: Entspricht beiden ABIs. Dies ist die Standardeinstellung für native_shared_libraries.

Die Attribute java, libraries und prebuilts sind ABI-unabhängig.

Dieses Beispiel gilt für ein Gerät, das 32/64 Bit unterstützt und 32 Bit nicht bevorzugt:

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-Signierung

Signieren Sie jedes APEX mit unterschiedlichen Schlüsseln. Wenn ein neuer Schlüssel erforderlich ist, erstellen Sie ein Schlüsselpaar aus öffentlichem und privatem Schlüssel und ein apex_key-Modul. Verwenden Sie das Attribut key, um das APEX mit dem Schlüssel zu signieren. Der öffentliche Schlüssel wird automatisch mit dem Namen avb_pubkey in den APEX aufgenommen.

# 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",
}

Im obigen Beispiel wird der Name des öffentlichen Schlüssels (foo) zur ID des Schlüssels. Die ID des Schlüssels, der zum Signieren eines APEX verwendet wird, ist im APEX enthalten. Zur Laufzeit prüft apexd den APEX mit einem öffentlichen Schlüssel mit derselben ID auf dem Gerät.

APEX-Signierung

APEX-Dateien werden auf dieselbe Weise signiert wie APKs. Signieren Sie APEX-Dateien zweimal: einmal für das Mini-Dateisystem (apex_payload.img-Datei) und einmal für die gesamte Datei.

Wenn Sie ein APEX auf Dateiebene signieren möchten, legen Sie das Attribut certificate auf eine der folgenden drei Arten fest:

  • Nicht festgelegt: Wenn kein Wert festgelegt ist, wird das APEX mit dem Zertifikat signiert, das sich unter PRODUCT_DEFAULT_DEV_CERTIFICATE befindet. Wenn kein Flag festgelegt ist, wird standardmäßig der Pfad build/target/product/security/testkey verwendet.
  • <name>: Das APEX ist mit dem <name>-Zertifikat im selben Verzeichnis wie PRODUCT_DEFAULT_DEV_CERTIFICATE signiert.
  • :<name>: Das APEX ist mit dem Zertifikat signiert, das durch das Soong-Modul mit dem Namen <name> definiert wird. Das Zertifikatsmodul kann so definiert werden:
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 installieren

Verwenden Sie ADB, um ein APEX zu installieren.

adb install apex_file_name
adb reboot

Wenn supportsRebootlessUpdate in apex_manifest.json auf true gesetzt ist und der aktuell installierte APEX nicht verwendet wird (z. B. alle darin enthaltenen Dienste wurden beendet), kann mit dem Flag --force-non-staged ein neuer APEX ohne Neustart installiert werden.

adb install --force-non-staged apex_file_name

APEX verwenden

Nach dem Neustart wird das APEX-Paket im Verzeichnis /apex/<apex_name>@<version> bereitgestellt. Es können mehrere Versionen desselben APEX gleichzeitig eingebunden werden. Unter den Mount-Pfaden wird der Pfad, der der neuesten Version entspricht, unter /apex/<apex_name> bind-gemountet.

Clients können den bind-gemounteten Pfad verwenden, um Dateien aus APEX zu lesen oder auszuführen.

APEX-Pakete werden in der Regel so verwendet:

  1. Ein OEM oder ODM lädt beim Versand des Geräts ein APEX unter /system/apex vor.
  2. Auf Dateien im APEX wird über den Pfad /apex/<apex_name>/ zugegriffen.
  3. Wenn eine aktualisierte Version des APEX in /data/apex installiert wird, verweist der Pfad nach dem Neustart auf den neuen APEX.

Dienst mit einem APEX aktualisieren

So aktualisieren Sie einen Dienst mit einem APEX:

  1. Markieren Sie den Dienst in der Systempartition als aktualisierbar. Fügen Sie der Dienstdefinition die Option updatable hinzu.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. Erstellen Sie eine neue .rc-Datei für den aktualisierten Dienst. Verwenden Sie die Option override, um den vorhandenen Dienst neu zu definieren.

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

Dienstdefinitionen können nur in der Datei .rc eines APEX definiert werden. Aktionsauslöser werden in APEXes nicht unterstützt.

Wenn ein als aktualisierbar gekennzeichneter Dienst vor der Aktivierung der APEX-Dateien gestartet wird, verzögert sich der Start, bis die Aktivierung der APEX-Dateien abgeschlossen ist.

System für APEX-Updates konfigurieren

Legen Sie das folgende Systemattribut auf true fest, um APEX-Dateiaktualisierungen zu unterstützen.

<device.mk>:

PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true

BoardConfig.mk:
TARGET_FLATTEN_APEX := false

oder einfach

<device.mk>:

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

Abgeflachte APEX

Bei älteren Geräten ist es manchmal unmöglich oder nicht praktikabel, den alten Kernel zu aktualisieren, um APEX vollständig zu unterstützen. Der Kernel wurde beispielsweise ohne CONFIG_BLK_DEV_LOOP=Y erstellt, was für das Bereitstellen des Dateisystem-Images in einem APEX entscheidend ist.

Ein zusammengefasstes APEX ist ein speziell entwickelter APEX, der auf Geräten mit einem Legacy-Kernel aktiviert werden kann. Dateien in einem zusammengeführten APEX werden direkt in einem Verzeichnis unter der integrierten Partition installiert. Beispiel: lib/libFoo.so in einem vereinfachten APEX-my.apex wird in /system/apex/my.apex/lib/libFoo.so installiert.

Beim Aktivieren eines zusammengeführten APEX ist das Loop-Gerät nicht beteiligt. Das gesamte Verzeichnis /system/apex/my.apex wird direkt an /apex/name@ver gebunden.

Abgeflachte APEX-Dateien können nicht aktualisiert werden, indem aktualisierte Versionen der APEX-Dateien aus dem Netzwerk heruntergeladen werden, da die heruntergeladenen APEX-Dateien nicht abgeflacht werden können. Zusammengeführte APEX-Module können nur über ein reguläres OTA-Update aktualisiert werden.

Die Standardkonfiguration ist „Flattened APEX“. Das bedeutet, dass alle APEX-Dateien standardmäßig zusammengeführt werden, sofern Sie Ihr Gerät nicht explizit so konfigurieren, dass nicht zusammengeführte APEX-Dateien erstellt werden, um APEX-Updates zu unterstützen (wie oben beschrieben).

Die Verwendung von komprimierten und nicht komprimierten APEX-Dateien auf einem Gerät wird NICHT unterstützt. APEX-Dateien auf einem Gerät müssen entweder alle nicht zusammengeführt oder alle zusammengeführt sein. Das ist besonders wichtig, wenn vorab signierte APEX-Prebuilts für Projekte wie Mainline ausgeliefert werden. APEX-Dateien, die nicht vorab signiert sind (d. h. aus dem Quellcode erstellt wurden), sollten ebenfalls nicht zusammengeführt und mit den richtigen Schlüsseln signiert werden. Das Gerät sollte von updatable_apex.mk erben, wie unter Dienst mit einem APEX aktualisieren beschrieben.

Komprimierte APEX-Dateien

In Android 12 und höher wird APEX-Komprimierung verwendet, um den Speicherbedarf von aktualisierbaren APEX-Paketen zu verringern. Nachdem ein Update für ein APEX installiert wurde, wird die vorinstallierte Version zwar nicht mehr verwendet, belegt aber weiterhin denselben Speicherplatz. Der belegte Speicherplatz bleibt weiterhin nicht verfügbar.

Die APEX-Komprimierung minimiert diese Auswirkungen auf den Speicher, indem ein stark komprimierter Satz von APEX-Dateien auf schreibgeschützten Partitionen (z. B. der /system-Partition) verwendet wird. Unter Android 12 und höher wird der DEFLATE-Algorithmus zur ZIP-Komprimierung verwendet.

Die Komprimierung bietet keine Optimierung für Folgendes:

  • Bootstrap-APEXs, die sehr früh im Bootvorgang eingebunden werden müssen.

  • APEX-Module, die nicht aktualisiert werden können. Die Komprimierung ist nur dann von Vorteil, wenn eine aktualisierte Version eines APEX auf der Partition /data installiert ist. Eine vollständige Liste der aktualisierbaren APEX-Module finden Sie auf der Seite Modulare Systemkomponenten.

  • APEX-Module für dynamische gemeinsam genutzte Bibliotheken. Da apexd immer beide Versionen solcher APEX-Module (vorinstalliert und aktualisiert) aktiviert, bringt das Komprimieren keinen Mehrwert.

Komprimiertes APEX-Dateiformat

Dies ist das Format einer komprimierten APEX-Datei.

Diagramm mit dem Format einer komprimierten APEX-Datei

Abbildung 2: Komprimiertes APEX-Dateiformat

Auf der obersten Ebene ist eine komprimierte APEX-Datei eine ZIP-Datei, die die ursprüngliche APEX-Datei in dekomprimierter Form mit einem Komprimierungsgrad von 9 und andere Dateien unkomprimiert enthält.

Eine APEX-Datei besteht aus vier Dateien:

  • original_apex: mit Komprimierungsstufe 9 entpackt Dies ist die ursprüngliche, unkomprimierte APEX-Datei.
  • apex_manifest.pb: Nur gespeichert
  • AndroidManifest.xml: Nur gespeichert
  • apex_pubkey: Nur gespeichert

Die Dateien apex_manifest.pb, AndroidManifest.xml und apex_pubkey sind Kopien der entsprechenden Dateien in original_apex.

Komprimierte APEX-Datei erstellen

Komprimierte APEX-Dateien können mit dem Tool apex_compression_tool.py unter system/apex/tools erstellt werden.

Im Build-System sind mehrere Parameter für die APEX-Komprimierung verfügbar.

In Android.bp wird gesteuert, ob eine APEX-Datei komprimierbar ist, indem das Attribut compressible verwendet wird:

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

Ein PRODUCT_COMPRESSED_APEX-Produkt-Flag steuert, ob ein aus dem Quellcode erstelltes Systemimage komprimierte APEX-Dateien enthalten muss.

Für lokale Tests können Sie erzwingen, dass APEX-Dateien komprimiert werden, indem Sie OVERRIDE_PRODUCT_COMPRESSED_APEX= auf true setzen.

Vom Build-System generierte komprimierte APEX-Dateien haben die Endung .capex. Die Erweiterung erleichtert die Unterscheidung zwischen komprimierten und unkomprimierten Versionen einer APEX-Datei.

Unterstützte Komprimierungsalgorithmen

Android 12 unterstützt nur die Deflate-Zip-Komprimierung.

Komprimierte APEX-Datei beim Booten aktivieren

Bevor ein komprimiertes APEX aktiviert werden kann, wird die Datei original_apex darin in das Verzeichnis /data/apex/decompressed dekomprimiert. Die dekomprimierte APEX-Datei wird mit dem Verzeichnis /data/apex/active verknüpft.

Das folgende Beispiel veranschaulicht den oben beschriebenen Prozess.

Stellen Sie sich /system/apex/com.android.foo.capex als komprimiertes APEX mit dem aktivierten versionCode 37 vor.

  1. Die Datei original_apex im Ordner /system/apex/com.android.foo.capex wird in /data/apex/decompressed/com.android.foo@37.apex dekomprimiert.
  2. restorecon /data/apex/decompressed/com.android.foo@37.apex wird ausgeführt, um zu prüfen, ob es ein korrektes SELinux-Label hat.
  3. Es werden Überprüfungen für /data/apex/decompressed/com.android.foo@37.apex durchgeführt, um die Gültigkeit zu bestätigen: apexd prüft den in /data/apex/decompressed/com.android.foo@37.apex enthaltenen öffentlichen Schlüssel, um zu bestätigen, dass er mit dem in /system/apex/com.android.foo.capex enthaltenen öffentlichen Schlüssel übereinstimmt.
  4. Die Datei /data/apex/decompressed/com.android.foo@37.apex ist fest mit dem Verzeichnis /data/apex/active/com.android.foo@37.apex verknüpft.
  5. Die reguläre Aktivierungslogik für unkomprimierte APEX-Dateien wird auf /data/apex/active/com.android.foo@37.apex ausgeführt.

Interaktion mit OTA

Komprimierte APEX-Dateien haben Auswirkungen auf die OTA-Bereitstellung und ‑Anwendung. Da ein OTA-Update eine komprimierte APEX-Datei mit einem höheren Versionsniveau als das auf einem Gerät aktive enthalten kann, muss vor dem Neustart eines Geräts zum Anwenden eines OTA-Updates ein bestimmter freier Speicherplatz reserviert werden.

Zur Unterstützung des OTA-Systems stellt apexd die folgenden beiden Binder-APIs bereit:

  • calculateSizeForCompressedApex: Berechnet die Größe, die zum Dekomprimieren von APEX-Dateien in einem OTA-Paket erforderlich ist. Damit kann überprüft werden, ob auf einem Gerät genügend Speicherplatz vorhanden ist, bevor ein OTA heruntergeladen wird.
  • reserveSpaceForCompressedApex – reserviert Speicherplatz auf der Festplatte für die zukünftige Verwendung durch apexd zum Dekomprimieren komprimierter APEX-Dateien im OTA-Paket.

Bei einem A/B-OTA-Update versucht apexd, die Dekomprimierung im Hintergrund als Teil der OTA-Routine nach der Installation durchzuführen. Wenn die Dekomprimierung fehlschlägt, führt apexd die Dekomprimierung während des Bootvorgangs durch, bei dem das OTA-Update angewendet wird.

Alternativen, die bei der Entwicklung von APEX in Betracht gezogen wurden

Hier sind einige Optionen, die bei der Entwicklung des APEX-Dateiformats in AOSP berücksichtigt wurden, und die Gründe, warum sie entweder einbezogen oder ausgeschlossen wurden.

Regelmäßige Paketverwaltungssysteme

Linux-Distributionen haben Paketverwaltungssysteme wie dpkg und rpm, die leistungsstark, ausgereift und robust sind. Sie wurden jedoch nicht für APEX übernommen, da sie die Pakete nach der Installation nicht schützen können. Die Überprüfung erfolgt nur, wenn Pakete installiert werden. Angreifer können die Integrität der installierten Pakete unbemerkt beeinträchtigen. Dies ist eine Regression für Android, bei der alle Systemkomponenten in schreibgeschützten Dateisystemen gespeichert wurden, deren Integrität durch dm-verity für jeden E/A-Vorgang geschützt ist. Manipulationen an Systemkomponenten müssen entweder verboten oder erkennbar sein, damit das Gerät den Start verweigern kann, wenn es manipuliert wurde.

dm-crypt für Integrität

Die Dateien in einem APEX-Container stammen aus integrierten Partitionen (z. B. der /system-Partition), die durch dm-verity geschützt sind. Jegliche Änderungen an den Dateien sind auch nach dem Mounten der Partitionen verboten. Um den Dateien das gleiche Sicherheitsniveau zu bieten, werden alle Dateien in einem APEX in einem Dateisystem-Image gespeichert, das mit einem Hash-Baum und einem vbmeta-Deskriptor gekoppelt ist. Ohne dm-verity ist ein APEX in der Partition /data anfällig für unbeabsichtigte Änderungen, die nach der Bestätigung und Installation vorgenommen werden.

Tatsächlich ist die /data-Partition auch durch Verschlüsselungsebenen wie dm-crypt geschützt. Dies bietet zwar einen gewissen Schutz vor Manipulationen, der primäre Zweck ist jedoch der Datenschutz, nicht die Integrität. Wenn ein Angreifer Zugriff auf die /data-Partition erhält, gibt es keinen weiteren Schutz. Dies ist wiederum ein Rückschritt im Vergleich dazu, dass sich jede Systemkomponente in der /system-Partition befindet. Der Hash-Baum in einer APEX-Datei bietet zusammen mit dm-verity das gleiche Maß an Inhaltsschutz.

Pfade von /system zu /apex umleiten

Auf Systemkomponentendateien, die in einem APEX verpackt sind, kann über neue Pfade wie /apex/<name>/lib/libfoo.so zugegriffen werden. Wenn die Dateien Teil der /system-Partition waren, konnte über Pfade wie /system/lib/libfoo.so darauf zugegriffen werden. Ein Client einer APEX-Datei (andere APEX-Dateien oder die Plattform) muss die neuen Pfade verwenden. Möglicherweise müssen Sie vorhandenen Code aufgrund der Pfadänderung aktualisieren.

Eine Möglichkeit, die Pfadänderung zu vermeiden, besteht darin, die Dateiinhalte in einer APEX-Datei auf die /system-Partition zu legen. Das Android-Team hat sich jedoch dagegen entschieden, Dateien auf die /system-Partition zu legen, da dies die Leistung beeinträchtigen könnte, wenn die Anzahl der Dateien, die überlagert werden (möglicherweise sogar übereinander), zunimmt.

Eine weitere Option bestand darin, Funktionen für den Dateizugriff wie open, stat und readlink zu manipulieren, sodass Pfade, die mit /system beginnen, zu den entsprechenden Pfaden unter /apex umgeleitet wurden. Das Android-Team hat diese Option verworfen, da es nicht möglich ist, alle Funktionen zu ändern, die Pfade akzeptieren. Einige Apps verknüpfen Bionic beispielsweise statisch, wodurch die Funktionen implementiert werden. In solchen Fällen werden diese Apps nicht weitergeleitet.