Format pliku APEX

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

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

Tło

Android obsługuje aktualizacje modułów, które pasują do standardowego modelu aplikacji (np. usług czy aktywności) za pomocą instalatorów aplikacji (np. Sklep Google Play), jednak korzystanie z podobnego modelu do komponentów systemu operacyjnego niższego poziomu ma te wady:

  • Modułów opartych na pliku APK nie można używać na wczesnym etapie sekwencji rozruchu. Menedżer pakietów to centralne repozytorium informacji o aplikacjach i można je uruchamiać wyłącznie z poziomu menedżera aktywności, który staje się gotowy na późniejszym etapie procedury uruchamiania.
  • Format APK (zwłaszcza plik manifestu) jest przeznaczony dla aplikacji na Androida, a moduły systemowe nie zawsze są odpowiednie.

Projektowanie

W tej sekcji opisano ogólną strukturę formatu pliku APEX oraz menedżera APEX, czyli usługę zarządzającą plikami APEX.

Więcej informacji o tym, dlaczego wybrany został ten model APEX, znajdziesz w artykule o alternatywach branych pod uwagę przy tworzeniu tej strategii.

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ą nieskompresowane i znajdują się na granicach 4 KB.

Cztery pliki w pliku APEX to:

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

Plik apex_manifest.json zawiera nazwę i wersję pakietu, które identyfikują plik APEX. To jest ApexManifestbufor protokołu w formacie JSON.

Plik AndroidManifest.xml umożliwia plikowi APEX korzystanie z narzędzi i infrastruktury związanych z plikami APK, takich jak ADB, PackageManager i aplikacje do instalowania pakietów (np. Sklep Play). Plik APEX może na przykład używać dotychczasowego narzędzia, takiego jak aapt, do sprawdzania podstawowych metadanych z pliku. Plik zawiera nazwę pakietu i informacje o wersji. Te informacje są ogólnie dostępne również w usłudze apex_manifest.json.

apex_manifest.json jest zalecany dla AndroidManifest.xml w przypadku nowego kodu i systemów obsługujących APEX. AndroidManifest.xml może zawierać dodatkowe informacje o kierowaniu, których mogą używać istniejące narzędzia do publikowania aplikacji.

apex_payload.img to obraz systemu plików ext4 z weryfikacją dm-verity. Obraz jest montowany w czasie działania za pomocą urządzenia pętli. Konkretnie drzewo haszy i blok metadanych są tworzone za pomocą biblioteki libavb. Ładunek systemu plików nie jest analizowany (ponieważ obraz powinien być możliwy do podłączenia). 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 gwarantuje, że pobrany punkt APEX jest podpisany tym samym elementem, który podpisuje ten sam punkt APEX we wbudowanych partycjach.

Wytyczne dotyczące nazewnictwa w APEX

Aby uniknąć konfliktów w nazwach nowych punktów APEX w miarę rozwoju platformy, stosuj się do tych wytycznych:

  • com.android.*
    • Zarezerwowane dla AOSP APEX. Nie jest unikalne dla żadnej firmy ani na żadnym urządzeniu.
  • com.<companyname>.*
    • Zarezerwowane dla firmy. Mogą być używane przez wiele urządzeń tej firmy.
  • com.<companyname>.<devicename>.*
    • Zarezerwowana dla punktów APEX, które są unikalne dla określonego urządzenia (lub podzbioru urządzeń).

Menedżer APEX

Menedżer APEX (lub apexd) to samodzielny natywny proces odpowiedzialny za weryfikowanie, instalowanie i odinstalowywanie plików APEX. Ten proces jest uruchamiany i gotowy na początku sekwencji uruchamiania. Pliki APEX są zwykle wstępnie instalowane na urządzeniu poniżej /system/apex. Jeśli nie ma żadnych aktualizacji, menedżer APEX domyślnie używa tych pakietów.

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

  1. Plik APEX jest pobierany za pomocą aplikacji do instalowania pakietów, ADB lub innego źródła.
  2. Menedżer pakietów rozpocznie procedurę instalacji. Po rozpoznaniu, że plik to APEX, menedżer pakietów przekazuje kontrolę do menedżera APEX.
  3. Menedżer APEX weryfikuje plik APEX.
  4. Po zweryfikowaniu pliku APEX wewnętrzna baza danych menedżera APEX jest aktualizowana tak, aby odzwierciedlała, że plik APEX jest aktywowany przy następnym uruchomieniu.
  5. Po pomyślnej weryfikacji pakietu żądający instalacji otrzymuje komunikat.
  6. Aby kontynuować instalację, należy ponownie uruchomić system.
  7. Podczas następnego uruchomienia menedżer APEX wczyta wewnętrzną bazę danych i wykonuje te czynności w przypadku każdego wymienionego pliku APEX:

    1. Weryfikuje plik APEX.
    2. Tworzy urządzenie pętli z pliku APEX.
    3. Tworzy urządzenie blokujące mapowanie urządzenia na urządzeniu pętli zwrotnej.
    4. Dodaje urządzenie do mapowania urządzeń na unikalnej ścieżce (na przykład /apex/name@ver).

Po podłączeniu wszystkich plików APEX wymienionych w wewnętrznej bazie danych menedżer APEX udostępnia usługę powiązania, która umożliwia innym komponentom systemowym wysyłanie zapytań 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 ścisłą ścieżkę, w której podłączony jest określony punkt APEX, aby można było uzyskać dostęp do plików.

Pliki APEX to pliki APK

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

Plik AndroidManifest.xml w pliku APEX jest krótki. Składa się z pakietu name i versionCode oraz (opcjonalnie) targetSdkVersion, minSdkVersion i maxSdkVersion do kierowania szczegółowego. Te informacje umożliwiają dostarczanie plików APEX za pomocą istniejących kanałów, takich jak aplikacje do instalowania pakietów i ADB.

Obsługiwane typy plików

Format APEX obsługuje te typy plików:

  • Natywne udostępniane biblioteki
  • Natywne pliki wykonywalne
  • Pliki JAR
  • Pliki danych
  • Pliki konfiguracyjne

Nie oznacza to, że APEX może aktualizować wszystkie te typy plików. To, czy typ pliku może zostać zaktualizowany, zależy od platformy i stabilności definicji interfejsów typów plików.

Opcje podpisywania

Pliki APEX są podpisywane na 2 sposoby. Najpierw plik apex_payload.img (czyli deskryptor vbmeta dołączony do apex_payload.img) jest podpisany kluczem. Następnie cały APEX jest podpisywany za pomocą schematu podpisu pliku APK w wersji 3. W tym procesie używane są 2 różne klucze.

Po stronie urządzenia zainstalowany jest klucz publiczny odpowiadający kluczowi prywatnemu służącemu do podpisywania deskryptora vbmeta. Menedżer APEX używa klucza publicznego do weryfikowania APEX, które mają zostać zainstalowane. Każdy plik APEX musi być podpisany za pomocą różnych kluczy i musi być egzekwowany zarówno w czasie kompilacji, jak i w czasie wykonywania.

APEX we wbudowanych partycjach

Pliki APEX mogą znajdować się we wbudowanych partycjach, np. /system. Partycja ma już ponad dm-verity, więc pliki APEX są podłączane bezpośrednio w urządzeniu pętli.

Jeśli we wbudowanej partycji znajduje się APEX, można go zaktualizować, przesyłając pakiet APEX z tą samą nazwą pakietu i kodem wersji większą lub równą. Nowy APEX jest przechowywany w pliku /data i podobnie jak w przypadku plików APK nowo zainstalowana wersja zastępuje wersję obecną w wbudowanej partycji. 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ć moduły główne APEX na urządzeniu z Androidem, wymagane są te funkcje jądra Linuxa: sterownik pętli zwrotnej i dm-verity. Sterownik pętli z powrotem montuje obraz systemu plików w module APEX, a dm-verity weryfikuje ten moduł.

Wydajność sterownika pętli i dm-verity ma duże znaczenie dla zapewnienia dobrej wydajności systemu przy korzystaniu z modułów APEX.

Obsługiwane wersje jądra

Moduł główny APEX jest obsługiwany na urządzeniach z jądrem w wersji 4.4 lub nowszej. Nowe urządzenia 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 obsługujące moduły APEX są zawarte w drzewie wspólnym Androida. Aby pobrać poprawki obsługujące APEX, użyj najnowszej wersji narzędzia Android Common Tree.

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 które mają obsługiwać moduły APEX. Aby uzyskać wymagane poprawki, zdecydowanie zalecamy użycie opcji scalania w dół z gałęzi android-4.4. Poniżej znajdziesz listę wymaganych indywidualnych poprawek dla wersji jądra 4.4.

  • UPSTREAM: pętla: dodaj ioctl, by zmienić rozmiar bloku logicznego (4.4)
  • BACKPORT: block/loop: set hw_sectors (4.4)
  • UPSTREAM: pętla: dodaj LOOP_SET_BLOCK_SIZE w zgodnym ioctl (4.4)
  • ANDROID: mnt: Napraw next_descendent (4.4)
  • ANDROID: mnt: remount powinna rozpowszechnić się wśród niewolników (4.4)
  • ANDROID: mnt: Poprawnie przeprowadź ponowną instalację (4.4)
  • Przywróć „ANDROID: dm verity: add minimum prefetch size” (4.4)
  • UPSTREAM: pętla: usuń pamięć podręczną, jeśli offset lub block_size uległy zmianie (4.4)

Wersje jądra 4.9/4.14/4.19

Aby uzyskać wymagane łaty na wersje jądra 4.9/4.14/4.19, złącz gałęzi android-common w dół.

Wymagane opcje konfiguracji jądra

Na liście poniżej znajdziesz podstawowe wymagania konfiguracyjne dotyczące obsługi modułów APEX wprowadzonych w Androidzie 10. Elementy oznaczone gwiazdką (*) to wymagania obowiązujące w Androidzie 9 i starszych wersjach.

(*) 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
  • loop.max_part musi być mniejsza niż 8

Tworzenie APEX

Z tej sekcji dowiesz się, jak utworzyć APEX za pomocą systemu kompilacji Androida. Poniżej znajdziesz przykład użycia funkcji Android.bp w przypadku obszaru 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",
}

Przykład strony apex_manifest.json:

{
  "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 i lokalizacje plików w APEX

Typ pliku Lokalizacja w APEX
Biblioteki udostępnione /lib/lib64 (/lib/arm w przypadku przetłumaczonego kodu na ARM w systemie x86)
Pliki wykonywalne /bin
biblioteki Java; /javalib
Gotowe rozwiązania /etc

Zależności pośrednie

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

Obsługa kilku interfejsów ABI

Zainstaluj właściwość native_shared_libs zarówno dla głównego, jak i dodatkowego interfejsu binarnego aplikacji (ABI) na urządzeniu. Jeśli punkt APEX jest kierowany na urządzenia z 1 interfejsem ABI (czyli tylko 32-bitowym lub tylko 64-bitowym), instalowane są tylko biblioteki z odpowiednim interfejsem ABI.

Zainstaluj właściwość binaries tylko dla głównego interfejsu ABI urządzenia w sposób opisany poniżej:

  • Jeśli urządzenie jest tylko 32-bitowe, zostanie zainstalowana tylko 32-bitowa wersja pliku binarnego.
  • Jeśli urządzenie ma tylko wersję 64-bitową, zainstalowana jest tylko wersja 64-bitowa pliku binarnego.

Aby zyskać szczegółową kontrolę nad interfejsami ABI bibliotek natywnych i plikami binarnymi, 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 ustawienie domyślne w przypadku plików binarnych.
  • lib32: dopasowuje się do 32-bitowego interfejsu ABI urządzenia, jeśli jest on obsługiwany.
  • lib64: odpowiada 64-bitowemu interfejsowi ABI obsługiwanego urządzenia.
  • prefer32: pasuje do 32-bitowego interfejsu ABI urządzenia, jeśli jest obsługiwany. Jeśli ABI 32-bitowy nie jest obsługiwany, pasuje do ABI 64-bitowego.
  • both: pasuje do obu interfejsów ABI. Jest to wartość domyślna w usłudze native_shared_libraries.

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

Ten przykład dotyczy urządzenia, które obsługuje 32/64 i nie preferuje 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
        },
    },
}

podpisywanie vbmeta

Podpisz każdy plik APEX za pomocą różnych kluczy. Gdy wymagany jest nowy klucz, utwórz parę kluczy publiczny-prywatny i moduł apex_key. Użyj właściwości key, aby podpisać APEX za pomocą klucza. Klucz publiczny jest automatycznie dołączany do Apex o nazwie 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 APEX jest zapisany w APEX. W czasie działania apexd weryfikuje APEX za pomocą klucza publicznego o tym samym identyfikatorze na urządzeniu.

Podpisywanie APEX

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

Aby podpisać 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, certyfikat APEX będzie podpisany certyfikatem znajdującym się pod adresem PRODUCT_DEFAULT_DEV_CERTIFICATE. Jeśli nie ustawisz żadnej flagi, ścieżka zostanie domyślnie ustawiona na build/target/product/security/testkey.
  • <name>: plik APEX jest podpisany certyfikatem <name> znajdującym się w tym samym katalogu co plik PRODUCT_DEFAULT_DEV_CERTIFICATE.
  • :<name>: APEX jest podpisany certyfikatem zdefiniowanym przez moduł utworu 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)
}

Zainstaluj APEX

Aby zainstalować APEX, użyj ADB.

adb install apex_file_name
adb reboot

Jeśli w parametry supportsRebootlessUpdate w pliku apex_manifest.json jest ustawiona wartość true, a obecnie zainstalowany plik APEX jest nieużywany (np. wszystkie usługi, które zawiera, zostały zatrzymane), nowy plik APEX można zainstalować bez ponownego uruchamiania komputera za pomocą flagi --force-non-staged.

adb install --force-non-staged apex_file_name

Używanie APEX

Po ponownym uruchomieniu interfejs APEX jest podłączany w katalogu /apex/<apex_name>@<version>. Jednocześnie można zamontować wiele wersji tego samego Apex. Spośród ścieżek do podłączenia ta, która odpowiada najnowszej wersji, jest zamontowana w miejscu /apex/<apex_name>.

Klienci mogą używać ścieżki podłączonej do powiązania do odczytu i wykonywania plików z APEX.

Punkty końcowe APEX są zwykle używane w taki sposób:

  1. OEM lub ODM wstępnie wczytuje APEX w sekcji /system/apex podczas wysyłki urządzenia.
  2. Do plików w APEX można uzyskać dostęp przez ścieżkę /apex/<apex_name>/.
  3. Gdy na urządzeniu /data/apex zainstalowana jest zaktualizowana wersja APEX, po ponownym uruchomieniu ścieżka wskazuje na nową wersję APEX.

Aktualizowanie usługi za pomocą APEX

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

  1. Oznacz usługę na partycji systemowej jako możliwą do aktualizacji. 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 zdefiniować tylko w pliku .rc w Apex. W APEX-ach nie są obsługiwane wyzwalacze działań.

Jeśli usługa oznaczona jako aktualizowana rozpocznie się przed aktywacją APEX, jej uruchomienie zostanie opóźnione do czasu zakończenia aktywacji APEX.

Skonfiguruj system do obsługi aktualizacji APEX

Aby obsługiwać aktualizacje plików APEX, ustaw tę 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ń zaktualizowanie starego jądra tak, aby w pełni obsługiwał APEX, jest niemożliwe lub niemożliwe. Na przykład jądro może być skompilowane bez CONFIG_BLK_DEV_LOOP=Y, co jest kluczowe dla zamontowania obrazu systemu plików w APEX.

Flattened APEX to specjalnie utworzona wersja APEX, którą można aktywować na urządzeniach ze starszym jądrem. Pliki w sprasowanym pliku APEX są instalowane bezpośrednio w katalogu wbudowanej partycji. Na przykład klucz lib/libFoo.so w płaskim punkcie APEXmy.apex jest instalowany w /system/apex/my.apex/lib/libFoo.so.

Aktywowanie płaskiego punktu APEX nie obejmuje urządzenia z pętlą. Cały katalog /system/apex/my.apex jest bezpośrednio połączony z /apex/name@ver.

Nie można aktualizować spłaszczonych APEX-ów przez pobranie z sieci zaktualizowanych wersji APEX-ów, ponieważ pobranych APEX-ów nie można spłaszczyć. Rozdzielone punkty APEX można aktualizować tylko za pomocą zwykłego OTA.

Spłaszczony APEX to konfiguracja domyślna. Oznacza to, że wszystkie punkty APK są domyślnie spłaszczone, chyba że jawnie skonfigurujesz urządzenie pod kątem tworzenia niespłaszczonych punktów na potrzeby obsługi aktualizacji APEX (jak wyjaśniono powyżej).

Mieszanie spłaszczonych i niespłaszczonych APEX na urządzeniu NIE jest obsługiwane. Punkty APEX w urządzeniu muszą być albo niespłaszczone, albo w całości spłaszczone. Jest to szczególnie ważne, gdy wysyłasz wstępnie podpisane pakiety APEX do projektów takich jak Mainline. APEX-y, które nie są wstępnie podpisane (czyli są kompilowane ze źródła), również nie powinny być spłaszczone i podpisane odpowiednimi kluczami. Urządzenie powinno dziedziczyć ustawienie updatable_apex.mk, zgodnie z opisem w sekcji Aktualizowanie usługi za pomocą punktu APEX.

Skompresowane APEX-y

Android 12 i nowsze wersje obsługują kompresję APEX, która zmniejsza wpływ na miejsce na dane w przypadku pakietów APEX z możliwością aktualizacji. Po zainstalowaniu aktualizacji Apex, chociaż fabrycznie zainstalowana wersja nie jest już używana, nadal zajmuje tyle samo miejsca. Zajmowane miejsce pozostaje niedostępne.

Kompresja APEX minimalizuje ten wpływ na miejsce na dane, używając bardzo skompresowanego zestawu plików APEX na partycjach tylko do odczytu (takich jak partycja /system). Android 12 i nowsze korzystają z algorytmu DEFLATE kompresji plików ZIP.

Kompresja nie zapewnia optymalizacji w tych przypadkach:

  • Wstępnie wczytuj punkty APEX, które muszą być zamontowane bardzo wcześnie w sekwencji rozruchu.

  • Nieaktualizowane pakiety APEX. Kompresja jest przydatna tylko wtedy, gdy w partycji /data zainstalowana jest zaktualizowana wersja APEX. Pełna lista komponentów APEX, które można aktualizować, jest dostępna na stronie Komponenty modułowego systemu.

  • Dynamic shared libs APEX. Ponieważ apexd zawsze aktywuje obie wersje takich APEX-ów (wstępnie zainstalowanych i uaktualnionych), ich kompresowanie nie zwiększa wartości.

skompresowany format pliku APEX,

Jest to format skompresowanego pliku APEX.

Diagram pokazujący format skompresowanego pliku APEX

Rysunek 2. skompresowany format pliku APEX,

Najwyższego poziomu skompresowany plik APEX to plik ZIP, który zawiera oryginalny plik apex w formie skróconej o poziomie kompresji 9 oraz innych plikach nieskompresowanych.

Cztery pliki składają się na plik APEX:

  • original_apex: spakowany z poziomem kompresji 9. Jest to oryginalny, niezpakowany plik APEX.
  • apex_manifest.pb: tylko zapisane
  • AndroidManifest.xml: tylko przechowywane
  • apex_pubkey: tylko przechowywane

Pliki apex_manifest.pb, AndroidManifest.xml i apex_pubkey to kopie odpowiadających im plików w usłudze original_apex.

Kompilowanie skompresowanego pliku APEX

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

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

W polu Android.bp o tym, czy plik APEX można skompresować, decyduje właściwość compressible:

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

Flaga produktu PRODUCT_COMPRESSED_APEX określa, czy obraz systemu utworzony na podstawie źródła musi zawierać skompresowane pliki APEX.

W przypadku eksperymentów lokalnych możesz wymusić kompresję przez kompresję punktów APEX przez kompresję, ustawiając 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 uruchamiania

Zanim będzie można aktywować skompresowany plik APEX, znajdujący się w nim plik original_apex zostanie zdekompresowany do katalogu /data/apex/decompressed. Wygenerowany plik APEX po rozpakowaniu jest twardo połączony z katalogiem /data/apex/active.

Poniżej znajdziesz przykład ilustrujący proces opisany powyżej.

Potraktuj /system/apex/com.android.foo.capex jako skompresowany APEX z kodem wersji 37.

  1. Plik original_apex wewnątrz /system/apex/com.android.foo.capex jest dekompresowany do formatu /data/apex/decompressed/com.android.foo@37.apex.
  2. Wykonywana jest operacja restorecon /data/apex/decompressed/com.android.foo@37.apex, aby sprawdzić, czy ma prawidłową etykietę SELinux.
  3. Aby sprawdzić ważność /data/apex/decompressed/com.android.foo@37.apex, przeprowadzane są weryfikacje: apexd sprawdza klucz publiczny w /data/apex/decompressed/com.android.foo@37.apex, aby potwierdzić, że jest on taki sam jak w /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 za pomocą /data/apex/active/com.android.foo@37.apex.

Interakcja z OTA

Kompresowane pliki APEX mają wpływ na dostarczanie i stosowanie 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 zapewnić obsługę systemu OTA, apexd udostępnia 2 interfejsy API powiązań:

  • calculateSizeForCompressedApex – oblicza rozmiar wymagany do rozpakowania plików APEX w pakiecie OTA. Można go użyć do sprawdzenia, czy na urządzeniu jest wystarczająco dużo miejsca przed pobraniem aktualizacji OTA.
  • reserveSpaceForCompressedApex – rezerwuje miejsce na dysku do wykorzystania w przyszłości przez apexd do dekompresji skompresowanych plików APEX w pakiecie OTA.

W przypadku aktualizacji OTA A/B apexd próbuje rozpakować pliki w tle w ramach rutyny OTA po instalacji. Jeśli dekompresja się nie powiedzie, apexd wykona dekompresję podczas uruchamiania, co spowoduje zastosowanie aktualizacji OTA.

Rozważane alternatywy podczas opracowywania APEX

Oto niektóre opcje, które AOSP wzięło pod uwagę podczas projektowania formatu pliku APEX, oraz powody ich uwzględnienia lub wykluczenia.

zwykłe systemy zarządzania pakietami,

Dystrybucje Linuksa zawierają systemy zarządzania pakietami, takie jak dpkg i rpm, które są wydajne, dopracowane i niezawodne. Nie zostały jednak zastosowane w przypadku APEX, ponieważ nie zapewniają ochrony pakietów po ich zainstalowaniu. Weryfikacja jest przeprowadzana tylko podczas instalowania pakietów. Hakerzy mogą niezauważyć naruszenia integralności zainstalowanych pakietów. Jest to regresja dla Androida, w której wszystkie komponenty systemu były przechowywane w systemach plików tylko do odczytu, których integralność jest chroniona przez dm-verity podczas każdego wejścia/wyjścia. Wszelkie ingerencje w elementy systemu muszą być zabronione lub możliwe do wykrycia, aby urządzenie mogło odmówić uruchomienia w przypadku przejęcia.

dm-crypt dla integralności.

Pliki w kontenerze APEX pochodzą z wbudowanych partycji (np. /system) chronionych przez dm-verity, przy czym jakiekolwiek modyfikacje plików są zabronione nawet po podłączeniu partycji. Aby zapewnić taki sam poziom bezpieczeństwa plików, wszystkie pliki w APEX są przechowywane w obrazie systemu plików sparowanym z drzewem haszu i deskryptorem vbmeta. Bez funkcji dm-verity moduł APEX w partycji /data jest narażony na niezamierzone modyfikacje wprowadzone 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 modyfikacją, jego głównym celem jest ochrona prywatności, a nie integralności. Gdy osoba przeprowadzająca atak uzyska dostęp do partycji /data, nie będzie już można jej chronić – jest to regresja w porównaniu z tym, że każdy komponent systemu znajduje się na partycji /system. Drzewo skrótów w pliku APEX w połączeniu z dm-verity zapewnia ten sam poziom ochrony treści.

Przekieruj ścieżki z /system do /apex

Pliki komponentów systemowych zapakowane w APEX są dostępne za pomocą nowych ścieżek, takich jak /apex/<name>/lib/libfoo.so. Gdy pliki znajdowały się w partycji /system, były dostępne pod ścieżkami takimi jak /system/lib/libfoo.so. Klient pliku APEX (inne pliki APEX lub platforma) musi używać nowych ścieżek. Zmiana ścieżki może wymagać zaktualizowania dotychczasowego kodu.

Chociaż jednym ze sposobów uniknięcia zmiany ścieżki jest nałożenie zawartości pliku na plik APEX na partycji /system, zespół Androida zdecydował, że nie będzie nakładać plików na partycji /system, ponieważ może to wpłynąć na wydajność, ponieważ liczba nakładanych plików (być może nawet ułożonych jeden na drugim) będzie rosła.

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