Format pliku APEX

Format kontenera Android Pony EXpress (APEX) został wprowadzony w Androidzie 10 i jest używany w procesie instalacji modułów systemowych niższego poziomu. Ten format ułatwia aktualizowanie komponentów systemu, które nie pasują do standardowego modelu aplikacji na Androida. Przykładowe komponenty to usługi i biblioteki natywne, warstwy abstrakcji sprzętu (HAL), środowisko wykonawcze (ART) i biblioteki klas.

Termin „APEX” może też odnosić się do pliku APEX.

Tło

Chociaż Android obsługuje aktualizacje modułów, które mieszczą się w standardowym modelu aplikacji (np. usług, aktywności) za pomocą aplikacji instalujących pakiety (takich jak aplikacja Sklep Google Play), używanie podobnego modelu w przypadku komponentów systemu operacyjnego niższego poziomu ma następujące wady:

  • Modułów opartych na plikach APK nie można używać na początku sekwencji rozruchu. Menedżer pakietów to centralne repozytorium informacji o aplikacjach. Można go uruchomić tylko z menedżera aktywności, który jest gotowy na późniejszym etapie procesu uruchamiania.
  • Format APK (zwłaszcza manifest) jest przeznaczony dla aplikacji na Androida, a moduły systemowe nie zawsze do niego pasują.

Projektowanie

W tej sekcji opisujemy ogólną strukturę formatu pliku APEX i menedżera APEX, czyli usługi zarządzającej plikami APEX.

Więcej informacji o tym, dlaczego wybrano ten projekt APEX, znajdziesz w artykule Alternatywne rozwiązania rozważane podczas tworzenia APEX.

Format APEX

Jest to format pliku APEX.

Format pliku APEX

Rysunek 1. Format pliku APEX

Na najwyższym poziomie plik APEX to plik ZIP, w którym pliki są przechowywane bez kompresji i znajdują się w granicach 4 KB.

Plik APEX zawiera 4 pliki:

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

Plik apex_manifest.json zawiera nazwę i wersję pakietu, które identyfikują plik APEX. Jest to ApexManifest bufor protokołu w formacie JSON.

Plik AndroidManifest.xml umożliwia korzystanie z narzędzi i infrastruktury związanych z APK, takich jak ADB, PackageManager i aplikacje do instalowania pakietów (np. Sklep Play). Na przykład plik APEX może używać istniejącego narzędzia, takiego jak aapt, do sprawdzania podstawowych metadanych z pliku. Plik zawiera nazwę pakietu i informacje o wersji. Te informacje są też zazwyczaj dostępne w apex_manifest.json.

W przypadku nowego kodu i systemów, które obsługują APEX, zalecamy używanie apex_manifest.json zamiast AndroidManifest.xml. AndroidManifest.xml może zawierać dodatkowe informacje o kierowaniu, które mogą być używane przez dotychczasowe narzędzia do publikowania aplikacji.

apex_payload.img to obraz systemu plików ext4 obsługiwany przez dm-verity. Obraz jest montowany w czasie działania za pomocą urządzenia zwrotnego. W szczególności drzewo skrótów i blok metadanych są tworzone za pomocą biblioteki libavb. Ładunek systemu plików nie jest analizowany (ponieważ obraz powinien być możliwy do zamontowania w miejscu). Zwykłe pliki są zawarte w pliku apex_payload.img.

apex_pubkey to klucz publiczny używany do podpisywania obrazu systemu plików. W czasie działania ten klucz zapewnia, że pobrany pakiet APEX jest podpisany przez ten sam podmiot, który podpisuje ten sam pakiet APEX w partycjach wbudowanych.

Wytyczne dotyczące nazewnictwa pakietów APEX

Aby zapobiec konfliktom nazw między nowymi pakietami APEX w miarę rozwoju platformy, postępuj zgodnie z tymi wskazówkami dotyczącymi nazewnictwa:

  • com.android.*
    • Zarezerwowane dla pakietów APEX AOSP. Nie jest unikalny dla żadnej firmy ani urządzenia.
  • com.<companyname>.*
    • Zarezerwowany dla firmy. Może być używany przez wiele urządzeń tej firmy.
  • com.<companyname>.<devicename>.*
    • Zarezerwowane dla pakietów APEX unikalnych dla konkretnego urządzenia (lub podzbioru urządzeń).

Menedżer APEX

Menedżer APEX (lub apexd) to samodzielny proces natywny odpowiedzialny za weryfikowanie, instalowanie i odinstalowywanie plików APEX. Ten proces jest uruchamiany i gotowy na wczesnym etapie sekwencji rozruchu. Pliki APEX są zwykle fabrycznie instalowane na urządzeniu w folderze /system/apex. Menedżer APEX domyślnie używa tych pakietów, jeśli nie są dostępne żadne aktualizacje.

Sekwencja aktualizacji pakietu APEX korzysta z klasy PackageManager i wygląda tak:

  1. Plik APEX jest pobierany za pomocą aplikacji instalatora pakietów, ADB lub innego źródła.
  2. Menedżer pakietów rozpocznie procedurę instalacji. Po rozpoznaniu, że plik jest pakietem APEX, menedżer pakietów przekazuje kontrolę do menedżera APEX.
  3. Menedżer APEX weryfikuje plik APEX.
  4. Jeśli plik APEX zostanie zweryfikowany, wewnętrzna baza danych menedżera APEX zostanie zaktualizowana, aby odzwierciedlać fakt, że plik APEX zostanie aktywowany przy następnym uruchomieniu.
  5. Osoba, która wysłała prośbę o instalację, otrzyma komunikat po pomyślnej weryfikacji pakietu.
  6. Aby kontynuować instalację, musisz ponownie uruchomić system.
  7. Przy następnym uruchomieniu menedżer APEX uruchamia się, odczytuje wewnętrzną bazę danych i wykonuje te czynności dla każdego wymienionego pliku APEX:

    1. Weryfikuje plik APEX.
    2. Tworzy urządzenie zwrotne z pliku APEX.
    3. Tworzy urządzenie blokowe mapowania urządzeń na urządzeniu zwrotnym.
    4. Montuje urządzenie blokowe mapowania urządzeń na unikalnej ścieżce (np. /apex/name@ver).

Gdy wszystkie pliki APEX wymienione w wewnętrznej bazie danych zostaną zamontowane, menedżer APEX udostępnia usługę Binder innym komponentom systemu, aby mogły one wysyłać zapytania o informacje o zainstalowanych plikach APEX. Na przykład inne komponenty systemu mogą wysyłać zapytania o listę plików APEX zainstalowanych na urządzeniu lub o dokładną ścieżkę, w której zamontowany jest konkretny pakiet APEX, aby można było uzyskać dostęp do plików.

Pliki APEX to pliki APK

Pliki APEX są prawidłowe, ponieważ są to podpisane archiwa ZIP (przy użyciu schematu podpisu APK) zawierające plik AndroidManifest.xml. Dzięki temu pliki APEX mogą korzystać z infrastruktury plików APK, takiej jak aplikacja instalatora pakietów, narzędzie do podpisywania i menedżer pakietów.

Plik AndroidManifest.xml w pliku APEX jest minimalny i zawiera pakiet name, versionCode oraz opcjonalne targetSdkVersion, minSdkVersionmaxSdkVersion do precyzyjnego kierowania. Te informacje umożliwiają dostarczanie plików APEX za pomocą istniejących kanałów, takich jak aplikacje instalujące pakiety i ADB.

Obsługiwane typy plików

Format APEX obsługuje te typy plików:

  • Natywne biblioteki udostępnione
  • Pliki wykonywalne natywne
  • Pliki JAR
  • Pliki danych
  • Pliki konfiguracji

Nie oznacza to, że APEX może aktualizować wszystkie te typy plików. Możliwość aktualizacji typu pliku zależy od platformy i stabilności definicji interfejsów typów plików.

Opcje podpisywania

Pliki APEX są podpisywane na 2 sposoby. Po pierwsze, plik apex_payload.img (a konkretnie deskryptor vbmeta dołączony do apex_payload.img) jest podpisywany kluczem. Następnie cały pakiet APEX jest podpisywany przy użyciu schematu podpisu APK w wersji 3. W tym procesie używane są 2 różne klucze.

Na urządzeniu jest zainstalowany klucz publiczny odpowiadający kluczowi prywatnemu użytemu do podpisania deskryptora vbmeta. Menedżer APEX-ów używa klucza publicznego do weryfikowania APEX-ów, które mają zostać zainstalowane. Każdy pakiet APEX musi być podpisany innymi kluczami. Wymaganie to jest egzekwowane zarówno podczas kompilacji, jak i w czasie działania.

APEX w partycjach wbudowanych

Pliki APEX mogą znajdować się na wbudowanych partycjach, np. /system. Partycja jest już w trybie dm-verity, więc pliki APEX są montowane bezpośrednio na urządzeniu zwrotnym.

Jeśli APEX znajduje się na wbudowanej partycji, można go zaktualizować, podając pakiet APEX o tej samej nazwie pakietu i kodzie wersji większym lub równym. Nowy pakiet APEX jest przechowywany w /data i podobnie jak w przypadku plików APK nowo zainstalowana wersja zastępuje wersję już obecną w wbudowanej partycji. Jednak w przeciwieństwie do plików APK nowo zainstalowana wersja APEX jest aktywowana dopiero po ponownym uruchomieniu.

Wymagania dotyczące jądra

Aby obsługiwać główne moduły APEX na urządzeniu z Androidem, wymagane są te funkcje jądra systemu Linux: sterownik loopback i dm-verity. Sterownik loopback montuje obraz systemu plików w module APEX, a dm-verity weryfikuje moduł APEX.

Wydajność sterownika pętli zwrotnej i dm-verity jest ważna dla uzyskania dobrej wydajności systemu podczas korzystania z modułów APEX.

Obsługiwane wersje jądra

Główne moduły APEX są obsługiwane na urządzeniach z jądrem w wersji 4.4 lub nowszej. Nowe urządzenia wprowadzane na rynek z Androidem 10 lub nowszym muszą używać jądra w wersji 4.9 lub nowszej, aby obsługiwać moduły APEX.

Wymagane poprawki jądra

Wymagane poprawki jądra do obsługi modułów APEX są zawarte w wspólnym drzewie Androida. Aby uzyskać poprawki obsługujące APEX, użyj najnowszej wersji wspólnego drzewa Androida.

Wersja jądra 4.4

Ta wersja jest obsługiwana tylko na urządzeniach, które zostały zaktualizowane z Androida 9 do Androida 10 i mają obsługiwać moduły APEX. Aby uzyskać wymagane poprawki, zdecydowanie zalecamy scalenie w dół z gałęzi android-4.4. Poniżej znajdziesz listę wymaganych pojedynczych poprawek dla wersji jądra 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)
  • Cofnięcie zmiany „ANDROID: dm verity: add minimum prefetch size” (4.4)
  • UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)

Wersje jądra 4.9/4.14/4.19

Aby uzyskać wymagane poprawki dla wersji jądra 4.9/4.14/4.19, pobierz je z gałęzi android-common.

Wymagane opcje konfiguracji jądra

Poniżej znajdziesz wymagania dotyczące konfiguracji podstawowej, które umożliwiają obsługę modułów APEX wprowadzonych w Androidzie 10. Elementy oznaczone gwiazdką (*) to dotychczasowe wymagania dotyczące Androida 9 i starszych wersji.

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

Wymagania dotyczące parametrów wiersza poleceń jądra

Aby obsługiwać APEX, upewnij się, że parametry wiersza poleceń jądra spełniają te wymagania:

  • loop.max_loop nie może być ustawiony
  • Wartość loop.max_part musi być mniejsza lub równa 8

Tworzenie APEX

W tej sekcji opisujemy, jak utworzyć pakiet APEX za pomocą systemu kompilacji Androida. Poniżej znajdziesz przykład Android.bp w przypadku APEX o nazwie 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 – przykład:

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

file_contexts – przykład:

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

Typy plików i lokalizacje w APEX

Typ pliku Lokalizacja w APEX
Biblioteki udostępnione /lib/lib64 (/lib/arm w przypadku przetłumaczonego ramienia w architekturze x86)
Pliki wykonywalne /bin
Biblioteki Java /javalib
Gotowe /etc

Zależności pośrednie

Pliki APEX automatycznie uwzględniają zależności przechodnie natywnych bibliotek współdzielonych lub plików wykonywalnych. Jeśli na przykład biblioteka libFoo zależy od biblioteki libBar, obie biblioteki są uwzględniane, gdy w właściwości native_shared_libs znajduje się tylko biblioteka libFoo.

Obsługa wielu interfejsów ABI

Zainstaluj usługę native_shared_libs zarówno w przypadku głównego, jak i dodatkowego interfejsu binarnego aplikacji (ABI) urządzenia. Jeśli pakiet APEX jest przeznaczony na urządzenia z jednym interfejsem ABI (czyli tylko 32-bitowym lub tylko 64-bitowym), instalowane są tylko biblioteki z odpowiednim interfejsem ABI.

Zainstaluj usługę binaries tylko dla głównego interfejsu ABI urządzenia, jak opisano poniżej:

  • Jeśli urządzenie jest tylko 32-bitowe, instalowana jest tylko 32-bitowa wersja pliku binarnego.
  • Jeśli urządzenie jest tylko 64-bitowe, instalowana jest tylko 64-bitowa wersja pliku binarnego.

Aby dodać szczegółową kontrolę nad interfejsami ABI bibliotek natywnych i plików binarnych, użyj właściwości multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries].

  • first: pasuje do głównego interfejsu ABI urządzenia. Jest to domyślne ustawienie plików binarnych.
  • lib32: pasuje do 32-bitowego interfejsu ABI urządzenia, jeśli jest obsługiwany.
  • lib64: pasuje do 64-bitowego interfejsu ABI urządzenia, jeśli jest obsługiwany.
  • prefer32: pasuje do 32-bitowego interfejsu ABI urządzenia, jeśli jest obsługiwany. Jeśli 32-bitowy interfejs ABI nie jest obsługiwany, jest zgodny z 64-bitowym interfejsem ABI.
  • both: pasuje do obu interfejsów ABI. Jest to ustawienie domyślne dla native_shared_libraries.

Właściwości java, librariesprebuilts są niezależne od interfejsu ABI.

Ten przykład dotyczy urządzenia, które obsługuje architekturę 32-bitową i 64-bitową, ale nie preferuje 32-bitowej:

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

podpisywanie vbmeta,

Podpisz każdy pakiet APEX innym kluczem. Gdy potrzebny jest nowy klucz, utwórz parę kluczy publiczny/prywatny i utwórz moduł apex_key. Użyj właściwości key, aby podpisać pakiet APEX za pomocą klucza. Klucz publiczny jest automatycznie dołączany do pakietu APEX pod nazwą 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",
}

W powyższym przykładzie nazwa klucza publicznego (foo) staje się identyfikatorem klucza. Identyfikator klucza użytego do podpisania pakietu APEX jest zapisany w tym pakiecie. W czasie działania apexd weryfikuje APEX przy użyciu klucza publicznego o tym samym identyfikatorze na urządzeniu.

Podpisywanie APEX

Podpisuj pakiety APEX w taki sam sposób jak pliki APK. Podpisz pliki APEX 2 razy: raz dla mini systemu plików (plik apex_payload.img) i raz dla całego pliku.

Aby podpisać pakiet APEX na poziomie pliku, ustaw właściwość certificate na jeden z tych 3 sposobów:

  • Nie ustawiono: jeśli nie ustawisz żadnej wartości, pakiet APEX zostanie podpisany certyfikatem znajdującym się w lokalizacji PRODUCT_DEFAULT_DEV_CERTIFICATE. Jeśli nie ustawisz żadnej flagi, ścieżka domyślnie będzie mieć wartość build/target/product/security/testkey.
  • <name>: Plik APEX jest podpisany certyfikatem <name> w tym samym katalogu co PRODUCT_DEFAULT_DEV_CERTIFICATE.
  • :<name>: plik APEX jest podpisany certyfikatem zdefiniowanym przez moduł Soong o nazwie <name>. Moduł certyfikatu można zdefiniować w ten sposób:
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)
}

Instalowanie pakietu APEX

Aby zainstalować APEX, użyj ADB.

adb install apex_file_name
adb reboot

Jeśli w apex_manifest.json parametr supportsRebootlessUpdate ma wartość true, a obecnie zainstalowany pakiet APEX nie jest używany (np. wszystkie usługi, które zawiera, zostały zatrzymane), można zainstalować nowy pakiet APEX bez ponownego uruchamiania urządzenia za pomocą flagi --force-non-staged.

adb install --force-non-staged apex_file_name

Używanie APEX

Po ponownym uruchomieniu pakiet APEX jest montowany w katalogu /apex/<apex_name>@<version>. Jednocześnie można zamontować kilka wersji tego samego pakietu APEX. Ścieżka montowania odpowiadająca najnowszej wersji jest montowana za pomocą polecenia bind w /apex/<apex_name>.

Klienci mogą używać ścieżki zamontowanej za pomocą funkcji bind, aby odczytywać lub wykonywać pliki z APEX.

Moduły APEX są zwykle używane w ten sposób:

  1. Producent OEM lub ODM wstępnie ładuje plik APEX w folderze /system/apex podczas wysyłki urządzenia.
  2. Dostęp do plików w APEX uzyskuje się za pomocą ścieżki /apex/<apex_name>/.
  3. Gdy w /data/apex zostanie zainstalowana zaktualizowana wersja APEX, ścieżka będzie wskazywać nowy APEX po ponownym uruchomieniu.

Aktualizowanie usługi za pomocą APEX

Aby zaktualizować usługę za pomocą pakietu APEX:

  1. Oznacz usługę w partycji systemowej jako możliwą do zaktualizowania. Dodaj opcjęupdatable do definicji usługi.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. Utwórz nowy plik .rc dla zaktualizowanej usługi. Użyj opcji override, aby ponownie zdefiniować istniejącą usługę.

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

Definicje usług można określać tylko w pliku .rc pakietu APEX. W przypadku APEX-ów wyzwalacze Action nie są obsługiwane.

Jeśli usługa oznaczona jako aktualizowana zostanie uruchomiona przed aktywacją pakietów APEX, jej uruchomienie zostanie opóźnione do momentu zakończenia aktywacji pakietów APEX.

Konfigurowanie systemu pod kątem obsługi aktualizacji APEX

Aby obsługiwać aktualizacje plików APEX, ustaw następującą właściwość systemu na true.

<device.mk>:

PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true

BoardConfig.mk:
TARGET_FLATTEN_APEX := false

lub po prostu

<device.mk>:

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

Spłaszczony APEX

W przypadku starszych urządzeń aktualizacja starego jądra w celu pełnej obsługi APEX jest czasami niemożliwa lub nieopłacalna. Na przykład jądro mogło zostać skompilowane bez CONFIG_BLK_DEV_LOOP=Y, co jest niezbędne do zamontowania obrazu systemu plików w APEX-ie.

Spłaszczony APEX to specjalnie utworzony pakiet APEX, który można aktywować na urządzeniach z starszym jądrem. Pliki w spłaszczonym pakiecie APEX są instalowane bezpośrednio w katalogu na wbudowanej partycji. Na przykład lib/libFoo.so w spłaszczonym pakiecie APEXmy.apex jest instalowany w /system/apex/my.apex/lib/libFoo.so.

Aktywacja spłaszczonego pakietu APEX nie wymaga urządzenia pętli. Cały katalog /system/apex/my.apex jest bezpośrednio zamontowany w /apex/name@ver.

Spłaszczonych pakietów APEX nie można aktualizować przez pobieranie z sieci ich zaktualizowanych wersji, ponieważ pobranych pakietów APEX nie można spłaszczyć. Spłaszczone pakiety APEX można aktualizować tylko za pomocą zwykłej aktualizacji OTA.

Domyślna konfiguracja to spłaszczony APEX. Oznacza to, że wszystkie pakiety APEX są domyślnie spłaszczone, chyba że skonfigurujesz urządzenie tak, aby tworzyć niespłaszczone pakiety APEX na potrzeby aktualizacji APEX (jak opisano powyżej).

Łączenie spłaszczonych i niespłaszczonych pakietów APEX na urządzeniu NIE jest obsługiwane. Wszystkie pakiety APEX na urządzeniu muszą być albo nieskompresowane, albo skompresowane. Jest to szczególnie ważne w przypadku wysyłania wstępnie podpisanych wstępnie skompilowanych pakietów APEX w projektach takich jak Mainline. Niespodpisane wstępnie pakiety APEX (czyli zbudowane ze źródła) również powinny być nieskompresowane i podpisane odpowiednimi kluczami. Urządzenie powinno dziedziczyć po updatable_apex.mk, jak wyjaśniono w artykule Aktualizowanie usługi za pomocą pakietu APEX.

Skompresowane pakiety APEX

Android 12 i nowsze wersje mają kompresję APEX, która zmniejsza wpływ aktualizowanych pakietów APEX na miejsce na dane. Po zainstalowaniu aktualizacji modułu APEX jego zainstalowana fabrycznie wersja nie jest już używana, ale nadal zajmuje tyle samo miejsca. Zajęte miejsce pozostaje niedostępne.

Kompresja APEX minimalizuje ten wpływ na pamięć, ponieważ wykorzystuje wysoce skompresowany zestaw plików APEX na partycjach tylko do odczytu (np. na partycji /system). Android 12 i nowsze wersje używają algorytmu kompresji ZIP DEFLATE.

Kompresja nie optymalizuje tych elementów:

  • Uruchamia APEX-y, które muszą być zamontowane na bardzo wczesnym etapie sekwencji rozruchu.

  • nieaktualizowanych pakietów APEX, Kompresja jest korzystna tylko wtedy, gdy zaktualizowana wersja pakietu APEX jest zainstalowana na partycji /data. Pełna lista APEX-ów, które można aktualizować, jest dostępna na stronie Komponenty systemu modułowego.

  • Dynamiczne biblioteki współdzielone APEX. Ponieważ apexd zawsze aktywuje obie wersje takich pakietów APEX (wstępnie zainstalowaną i uaktualnioną), ich kompresowanie nie przynosi żadnych korzyści.

Skompresowany format pliku APEX

Jest to format skompresowanego pliku APEX.

Diagram przedstawia format skompresowanego pliku APEX.

Rysunek 2. Skompresowany format pliku APEX

Skompresowany plik APEX najwyższego poziomu to plik ZIP zawierający oryginalny plik APEX w postaci zdekompresowanej z poziomem kompresji 9 oraz inne pliki przechowywane bez kompresji.

Plik APEX składa się z 4 plików:

  • original_apex: deflated with compression level of 9 To jest oryginalny, nieskompresowany plik APEX.
  • apex_manifest.pb: tylko przechowywane
  • AndroidManifest.xml: tylko przechowywane
  • apex_pubkey: tylko przechowywane

Pliki apex_manifest.pb, AndroidManifest.xmlapex_pubkey są kopiami odpowiednich plików w usłudze original_apex.

Kompilowanie skompresowanego pakietu APEX

Skompresowany pakiet APEX można utworzyć za pomocą narzędzia apex_compression_tool.py znajdującego się w lokalizacji system/apex/tools.

W systemie kompilacji dostępnych jest kilka parametrów związanych z kompresją APEX.

Android.bp to, czy plik APEX jest kompresowalny, jest kontrolowane przez właściwość compressible:

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

PRODUCT_COMPRESSED_APEXFlaga produktu określa, czy obraz systemu utworzony na podstawie kodu źródłowego musi zawierać skompresowane pliki APEX.

W przypadku eksperymentów lokalnych możesz wymusić kompresję pakietów APEX, ustawiając wartość OVERRIDE_PRODUCT_COMPRESSED_APEX= na true.

Skompresowane pliki APEX wygenerowane przez system kompilacji mają rozszerzenie .capex. Rozszerzenie ułatwia odróżnianie skompresowanych i nieskompresowanych wersji pliku APEX.

Obsługiwane algorytmy kompresji

Android 12 obsługuje tylko kompresję deflate-zip.

Aktywowanie skompresowanego pliku APEX podczas rozruchu

Zanim skompresowany pakiet APEX zostanie aktywowany, plik original_apex w nim zawarty jest dekompresowany do katalogu /data/apex/decompressed. Powstały zdekompresowany plik APEX jest połączony na stałe z katalogiem /data/apex/active.

Poniższy przykład ilustruje opisany powyżej proces.

Rozważmy /system/apex/com.android.foo.capex jako skompresowany pakiet APEX, który jest aktywowany z kodem wersji 37.

  1. Plik original_apex w lokalizacji /system/apex/com.android.foo.capex zostanie rozpakowany do folderu /data/apex/decompressed/com.android.foo@37.apex.
  2. restorecon /data/apex/decompressed/com.android.foo@37.apex jest wykonywane w celu sprawdzenia, czy ma prawidłową etykietę SELinux.
  3. Weryfikacja jest przeprowadzana na /data/apex/decompressed/com.android.foo@37.apex, aby sprawdzić jego ważność:apexd sprawdza klucz publiczny dołączony do /data/apex/decompressed/com.android.foo@37.apex, aby upewnić się, że jest on równy kluczowi dołączonemu do /system/apex/com.android.foo.capex.
  4. Plik /data/apex/decompressed/com.android.foo@37.apex jest połączony na stałe z katalogiem /data/apex/active/com.android.foo@37.apex.
  5. Zwykła logika aktywacji nieskompresowanych plików APEX jest wykonywana na urządzeniu /data/apex/active/com.android.foo@37.apex.

Interakcja z biurem podróży online

Skompresowane pliki APEX mają wpływ na dostarczanie i stosowanie aktualizacji OTA. Aktualizacja OTA może zawierać skompresowany plik APEX o wyższym poziomie wersji niż ten, który jest aktywny na urządzeniu, dlatego przed ponownym uruchomieniem urządzenia w celu zastosowania aktualizacji OTA należy zarezerwować pewną ilość wolnego miejsca.

Aby obsługiwać system OTA, apexd udostępnia te 2 interfejsy API usługi Binder:

  • calculateSizeForCompressedApex – oblicza rozmiar wymagany do zdekompresowania plików APEX w pakiecie OTA. Może to służyć do sprawdzania, czy na urządzeniu jest wystarczająco dużo miejsca przed pobraniem aktualizacji OTA.
  • reserveSpaceForCompressedApex – rezerwuje miejsce na dysku na potrzeby przyszłego użycia przez apexd do dekompresji skompresowanych plików APEX w pakiecie OTA.

W przypadku aktualizacji OTA typu A/B system apexd próbuje zdekompresować dane w tle w ramach procedury poinstalacyjnej. Jeśli dekompresja się nie powiedzie,apexd przeprowadza dekompresję podczas uruchamiania, które stosuje aktualizację OTA.

Alternatywy rozważane podczas tworzenia APEX

Oto kilka opcji, które zespół AOSP rozważał podczas projektowania formatu pliku APEX, oraz powody ich uwzględnienia lub wykluczenia.

Zwykłe systemy zarządzania pakietami

Dystrybucje Linuksa mają systemy zarządzania pakietami, takie jak dpkg i rpm, które są zaawansowane, dojrzałe i niezawodne. Nie zostały one jednak zaadaptowane w przypadku pakietów APEX, ponieważ nie mogą chronić pakietów po zainstalowaniu. Weryfikacja jest przeprowadzana tylko podczas instalowania pakietów. Osoby przeprowadzające atak mogą niezauważenie naruszyć integralność zainstalowanych pakietów. W przypadku Androida jest to regresja, ponieważ wszystkie komponenty systemu były przechowywane w systemach plików tylko do odczytu, których integralność była chroniona przez dm-verity w przypadku każdego wejścia/wyjścia. Wszelkie ingerencje w komponenty systemu muszą być zabronione lub wykrywalne, aby w przypadku naruszenia bezpieczeństwa urządzenie mogło odmówić uruchomienia.

dm-crypt na potrzeby integralności

Pliki w kontenerze APEX pochodzą z wbudowanych partycji (np. partycji /system), które są chronione przez dm-verity. Wszelkie modyfikacje plików są zabronione nawet po zamontowaniu partycji. Aby zapewnić plikom ten sam poziom bezpieczeństwa, wszystkie pliki w pakiecie APEX są przechowywane w obrazie systemu plików, który jest powiązany z drzewem skrótów i deskryptorem vbmeta. Bez dm-verity pakiet APEX w partycji /data jest podatny na niezamierzone modyfikacje, które są wprowadzane po jego zweryfikowaniu i zainstalowaniu.

W rzeczywistości partycja /data jest też chroniona przez warstwy szyfrowania, takie jak dm-crypt. Chociaż zapewnia to pewien poziom ochrony przed manipulacją, jego głównym celem jest ochrona prywatności, a nie integralności. Gdy atakujący uzyska dostęp do partycji /data, nie można już zastosować żadnych zabezpieczeń. Jest to regres w porównaniu z sytuacją, w której wszystkie komponenty systemu znajdują się na partycji /system. Drzewo skrótów w pliku APEX wraz z dm-verity zapewnia ten sam poziom ochrony treści.

Przekierowywanie ścieżek z /system do /apex

Pliki komponentów systemowych spakowane w APEX-ie są dostępne za pomocą nowych ścieżek, np. /apex/<name>/lib/libfoo.so. Jeśli pliki były częścią partycji /system, były dostępne za pomocą ścieżek takich jak /system/lib/libfoo.so. Klient pliku APEX (inne pliki APEX lub platforma) musi używać nowych ścieżek. W wyniku zmiany ścieżki może być konieczna aktualizacja istniejącego kodu.

Chociaż jednym ze sposobów na uniknięcie zmiany ścieżki jest nałożenie zawartości pliku APEX na partycję /system, zespół Androida zdecydował się nie nakładać plików na partycję /system, ponieważ mogłoby to wpłynąć na wydajność w miarę zwiększania się liczby nakładanych plików (być może nawet ułożonych jeden na drugim).

Innym rozwiązaniem było przejęcie funkcji dostępu do plików, takich jak open, statreadlink, tak aby ścieżki rozpoczynające się od /system były przekierowywane do odpowiednich ścieżek w /apex. Zespół Androida odrzucił tę opcję, ponieważ nie można zmienić wszystkich funkcji, które akceptują ścieżki. Niektóre aplikacje statycznie łączą się z biblioteką Bionic, która implementuje te funkcje. W takich przypadkach aplikacje nie są przekierowywane.