Formato de arquivo APEX

O formato de contêiner Android Pony EXpress (APEX) foi introduzido no Android 10 e é usado no fluxo de instalação para módulos de sistema de nível inferior. Esse formato facilita as atualizações de componentes do sistema que não se encaixam no modelo de aplicativo Android padrão. Alguns exemplos de componentes são serviços e bibliotecas nativas, camadas de abstração de hardware (HALs), ambiente de execução (ART) e bibliotecas de classes.

O termo "APEX" também pode se referir a um arquivo APEX.

Contexto

Embora o Android ofereça suporte a atualizações de módulos que se encaixam no modelo de app padrão (por exemplo, serviços, atividades) por apps de instalação de pacotes (como o app da Google Play Store), o uso de um modelo semelhante para componentes de SO de nível inferior tem as seguintes desvantagens:

  • Módulos baseados em APK não podem ser usados no início da sequência de inicialização. O gerenciador de pacotes é o repositório central de informações sobre apps e só pode ser iniciado pelo gerenciador de atividades, que fica pronto em uma fase posterior do procedimento de inicialização.
  • O formato do APK, principalmente o manifesto, foi projetado para apps Android, e os módulos do sistema nem sempre são adequados.

Design

Esta seção descreve o design de alto nível do formato de arquivo APEX e o gerenciador APEX, que é um serviço que gerencia arquivos APEX.

Para mais informações sobre por que esse design foi selecionado para o APEX, consulte Alternativas consideradas ao desenvolver o APEX.

Formato APEX

Esse é o formato de um arquivo APEX.

Formato de arquivo APEX

Figura 1. Formato de arquivo APEX

No nível superior, um arquivo APEX é um arquivo ZIP em que os arquivos são armazenados sem compactação e localizados em limites de 4 KB.

Os quatro arquivos em um arquivo APEX são:

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

O arquivo apex_manifest.json contém o nome do pacote e a versão, que identificam um arquivo APEX. Este é um buffer de protocolo ApexManifest no formato JSON.

O arquivo AndroidManifest.xml permite que o arquivo APEX use ferramentas e infraestruturas relacionadas ao APK, como ADB, PackageManager e apps de instalação de pacotes (como a Play Store). Por exemplo, o arquivo APEX pode usar uma ferramenta existente, como aapt, para inspecionar metadados básicos do arquivo. O arquivo contém o nome do pacote e informações de versão. Essas informações geralmente também estão disponíveis em apex_manifest.json.

Recomendamos o uso de apex_manifest.json em vez de AndroidManifest.xml para novos códigos e sistemas que lidam com o APEX. O AndroidManifest.xml pode conter outras informações de segmentação que podem ser usadas pelas ferramentas de publicação de apps atuais.

apex_payload.img é uma imagem do sistema de arquivos ext4 com suporte do dm-verity. A imagem é montada no momento da execução por um dispositivo de loopback. Especificamente, a árvore de hash e o bloco de metadados são criados usando a biblioteca libavb. O payload do sistema de arquivos não é analisado porque a imagem precisa ser montada no local. Arquivos regulares estão incluídos dentro do arquivo apex_payload.img.

apex_pubkey é a chave pública usada para assinar a imagem do sistema de arquivos. No ambiente de execução, essa chave garante que o APEX transferido seja assinado com a mesma entidade que assina o mesmo APEX nas partições integradas.

Diretrizes de nomenclatura APEX

Para evitar conflitos de nomenclatura entre novos APEXs à medida que a plataforma avança, use as seguintes diretrizes de nomenclatura:

  • com.android.*
    • Reservado para APEXs do AOSP. Não é exclusivo de nenhuma empresa ou dispositivo.
  • com.<companyname>.*
    • Reservado para uma empresa. Potencialmente usado por vários dispositivos da empresa.
  • com.<companyname>.<devicename>.*
    • Reservado para APEXes exclusivos de um dispositivo específico (ou subconjunto de dispositivos).

Gerenciador APEX

O gerenciador APEX (ou apexd) é um processo nativo independente responsável por verificar, instalar e desinstalar arquivos APEX. Esse processo é iniciado e fica pronto no início da sequência de inicialização. Os arquivos APEX normalmente são pré-instalados no dispositivo em /system/apex. O gerenciador APEX usa esses pacotes por padrão se nenhuma atualização estiver disponível.

A sequência de atualização de um APEX usa a classe PackageManager e é a seguinte.

  1. Um arquivo APEX é transferido por download por um app instalador de pacote, pelo ADB ou por outra fonte.
  2. O gerenciador de pacotes inicia o procedimento de instalação. Ao reconhecer que o arquivo é um APEX, o gerenciador de pacotes transfere o controle para o gerenciador APEX.
  3. O gerenciador APEX verifica o arquivo APEX.
  4. Se o arquivo APEX for verificado, o banco de dados interno do gerenciador APEX será atualizado para refletir que o arquivo APEX será ativado na próxima inicialização.
  5. O solicitante de instalação recebe uma transmissão após a verificação do pacote.
  6. Para continuar a instalação, reinicie o sistema.
  7. Na próxima inicialização, o gerenciador APEX é iniciado, lê o banco de dados interno e faz o seguinte para cada arquivo APEX listado:

    1. Verifica o arquivo APEX.
    2. Cria um dispositivo de loopback com base no arquivo APEX.
    3. Cria um dispositivo de bloco mapeador de dispositivo sobre o dispositivo de loopback.
    4. Monta o dispositivo de bloco mapeador de dispositivo em um caminho exclusivo (por exemplo, /apex/name@ver).

Quando todos os arquivos APEX listados no banco de dados interno são montados, o gerenciador APEX fornece um serviço de vinculação para outros componentes do sistema consultarem informações sobre os arquivos APEX instalados. Por exemplo, os outros componentes do sistema podem consultar a lista de arquivos APEX instalados no dispositivo ou o caminho exato em que um APEX específico é montado, para que os arquivos possam ser acessados.

Arquivos APEX são arquivos APK

Os arquivos APEX são arquivos APK válidos porque são arquivos ZIP assinados (usando o esquema de assinatura de APK) que contêm um arquivo AndroidManifest.xml. Isso permite que os arquivos APEX usem a infraestrutura de arquivos APK, como um app instalador de pacotes, o utilitário de assinatura e o gerenciador de pacotes.

O arquivo AndroidManifest.xml em um arquivo APEX é mínimo, consistindo no pacote name, versionCode e targetSdkVersion, minSdkVersion e maxSdkVersion opcionais para segmentação detalhada. Com essas informações, os arquivos APEX podem ser enviados por canais, como apps de instalação de pacotes e ADB.

Tipos de arquivo compatíveis

O formato APEX oferece suporte a estes tipos de arquivo:

  • Bibliotecas compartilhadas nativas
  • Executáveis nativos
  • Arquivos JAR
  • Arquivos de dados
  • Arquivos de configuração

Isso não significa que o APEX pode atualizar todos esses tipos de arquivo. A atualização de um tipo de arquivo depende da plataforma e da estabilidade das definições das interfaces para os tipos de arquivo.

Opções de assinatura

Os arquivos APEX são assinados de duas maneiras. Primeiro, o arquivo apex_payload.img (especificamente, o descritor vbmeta anexado a apex_payload.img) é assinado com uma chave. Em seguida, todo o APEX é assinado usando o esquema de assinatura de APK v3. Duas chaves diferentes são usadas neste processo.

No lado do dispositivo, uma chave pública correspondente à chave privada usada para assinar o descritor vbmeta é instalada. O gerenciador de APEX usa a chave pública para verificar os APEXs que são solicitados para instalação. Cada APEX precisa ser assinado com chaves diferentes e aplicado no tempo de build e na execução.

APEX em partições integradas

Os arquivos APEX podem estar localizados em partições integradas, como /system. A partição já está acima de dm-verity, então os arquivos APEX são montados diretamente no dispositivo de loopback.

Se um APEX estiver presente em uma partição integrada, ele poderá ser atualizado fornecendo um pacote APEX com o mesmo nome de pacote e um código de versão maior ou igual a. O novo APEX é armazenado em /data e, semelhante aos APKs, a versão recém-instalada oculta a versão já presente na partição integrada. No entanto, ao contrário dos APKs, a versão recém-instalada do APEX só é ativada após a reinicialização.

Requisitos do kernel

Para oferecer suporte a módulos de linha principal APEX em um dispositivo Android, os seguintes recursos do kernel do Linux são necessários: o driver de loopback e o dm-verity. O driver de loopback monta a imagem do sistema de arquivos em um módulo APEX e o dm-verity verifica o módulo APEX.

O desempenho do driver de loopback e do dm-verity é importante para garantir um bom desempenho do sistema ao usar módulos APEX.

Versões do kernel com suporte

Os módulos principais do APEX têm suporte em dispositivos que usam versões 4.4 ou mais recentes do kernel. Os novos dispositivos lançados com o Android 10 ou versões mais recentes precisam usar a versão 4.9 ou mais recente do kernel para oferecer suporte a módulos APEX.

Patches do kernel necessários

Os patches de kernel necessários para oferecer suporte a módulos APEX estão incluídos na árvore comum do Android. Para que os patches ofereçam suporte ao APEX, use a versão mais recente da árvore comum do Android.

Versão do kernel 4.4

Essa versão só oferece suporte a dispositivos que fizeram upgrade do Android 9 para o Android 10 e querem oferecer suporte a módulos APEX. Para conseguir os patches necessários, é altamente recomendável fazer uma downmerge da ramificação android-4.4. Confira a seguir uma lista dos patches individuais necessários para a versão 4.4 do kernel.

  • UPSTREAM: loop: add ioctl para alterar o tamanho do bloco lógico (4.4)
  • BACKPORT: block/loop: set hw_sectors (4.4)
  • UPSTREAM: loop: foi adicionado LOOP_SET_BLOCK_SIZE ao Compatível com ioctl (4.4)
  • ANDROID: mnt: correção de next_descendent (4.4).
  • ANDROID: mnt: a remontagem precisa ser propagada para escravos de escravos (4.4).
  • ANDROID: mnt: propaga a remontagem corretamente (4.4).
  • Reversão de "ANDROID: dm verity: add minimum prefetch size" (4.4).
  • UPSTREAM: loop: descarta caches se o deslocamento ou o block_size forem alterados (4.4).

Versões do kernel 4.9/4.14/4.19

Para receber os patches necessários para as versões 4.9/4.14/4.19 do kernel, faça uma mesclagem para baixo do ramo android-common.

Opções de configuração de kernel obrigatórias

A lista a seguir mostra os requisitos de configuração básica para oferecer suporte a módulos APEX introduzidos no Android 10. Os itens com um asterisco (*) são requisitos do Android 9 e versões anteriores.

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

Requisitos de parâmetro de linha de comando do kernel

Para oferecer suporte ao APEX, verifique se os parâmetros de linha de comando do kernel atendem aos seguintes requisitos:

  • loop.max_loop NÃO pode ser definido
  • O valor de loop.max_part precisa ser menor ou igual a 8

Criar um APEX

Esta seção descreve como criar um APEX usando o sistema de build do Android. Veja a seguir um exemplo de Android.bp para um APEX chamado 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",
}

Exemplo de apex_manifest.json:

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

Exemplo de file_contexts:

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

Tipos de arquivo e locais no APEX

Tipo de arquivo Local no APEX
Bibliotecas compartilhadas /lib e /lib64 (/lib/arm para o grupo traduzido em x86)
Executáveis /bin
Bibliotecas Java /javalib
Pré-versões /etc

Dependências transitivas

Os arquivos APEX incluem automaticamente dependências transitivas de bibliotecas compartilhadas nativas ou executáveis. Por exemplo, se libFoo depender de libBar, as duas bibliotecas serão incluídas quando apenas libFoo estiver listado na propriedade native_shared_libs.

Lidar com várias ABIs

Instale a propriedade native_shared_libs para as interfaces binárias de aplicativo (ABIs, na sigla em inglês) primárias e secundárias do dispositivo. Se um APEX for destinado a dispositivos com uma única ABI (ou seja, somente 32 bits ou 64 bits), somente as bibliotecas com a ABI correspondente serão instaladas.

Instale a propriedade binaries apenas para a ABI principal do dispositivo, conforme descrito abaixo:

  • Se o dispositivo for de apenas 32 bits, apenas a variante de 32 bits do binário será instalada.
  • Se o dispositivo tiver apenas 64 bits, apenas a variante de 64 bits do binário será instalada.

Para adicionar um controle refinado sobre as ABIs das bibliotecas nativas e dos binários, use as propriedades multilib.[first|lib32|lib64|prefer32|both].[native_shared_libs|binaries].

  • first: corresponde à ABI principal do dispositivo. Esse é o padrão para binários.
  • lib32: corresponde à ABI de 32 bits do dispositivo, se compatível.
  • lib64: corresponde à ABI de 64 bits do dispositivo, com suporte.
  • prefer32: corresponde à ABI de 32 bits do dispositivo, se compatível. Se a ABI de 32 bits não tiver suporte, será feita a correspondência com a ABI de 64 bits.
  • both: corresponde a ambas as ABIs. Esse é o padrão para native_shared_libraries.

As propriedades java, libraries e prebuilts não dependem da ABI.

Este exemplo é para um dispositivo que oferece suporte a 32/64 e não prefere 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
        },
    },
}

assinatura vbmeta

Assine cada APEX com chaves diferentes. Quando uma nova chave for necessária, crie um par de chaves pública e privada e crie um módulo apex_key. Use a propriedade key para assinar o APEX usando a chave. A chave pública é incluída automaticamente no APEX com o nome 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",
}

No exemplo acima, o nome da chave pública (foo) se torna o ID da chave. O ID da chave usada para assinar um APEX está escrito no APEX. No momento da execução, apexd verifica o APEX usando uma chave pública com o mesmo ID no dispositivo.

Assinatura APEX

Assine APEXs da mesma forma que você assina APKs. Assine o APEX duas vezes: uma para o minisistema de arquivos (arquivo apex_payload.img) e outra para o arquivo inteiro.

Para assinar um APEX no nível do arquivo, defina a propriedade certificate de uma destas três maneiras:

  • Não definido: se nenhum valor for definido, o APEX será assinado com o certificado localizado em PRODUCT_DEFAULT_DEV_CERTIFICATE. Se nenhuma flag for definida, o caminho padrão será build/target/product/security/testkey.
  • <name>: o APEX é assinado com o certificado <name> no mesmo diretório que PRODUCT_DEFAULT_DEV_CERTIFICATE.
  • :<name>: o APEX é assinado com o certificado definido pelo módulo Soong chamado <name>. O módulo de certificado pode ser definido da seguinte maneira.
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)
}

Instalar um APEX

Para instalar um APEX, use o ADB.

adb install apex_file_name
adb reboot

Se supportsRebootlessUpdate estiver definido como true em apex_manifest.json e o APEX instalado atualmente não estiver em uso (por exemplo, todos os serviços que ele contém foram interrompidos), um novo APEX poderá ser instalado sem uma reinicialização com a flag --force-non-staged.

adb install --force-non-staged apex_file_name

Usar um APEX

Após a reinicialização, o APEX é montado no diretório /apex/<apex_name>@<version>. Várias versões do mesmo APEX podem ser montadas ao mesmo tempo. Entre os caminhos de ativação, aquele que corresponde à versão mais recente é vinculado em /apex/<apex_name>.

Os clientes podem usar o caminho ativado de vinculação para ler ou executar arquivos do APEX.

Os APEXes geralmente são usados da seguinte maneira:

  1. Um OEM ou ODM pré-carrega um APEX em /system/apex quando o dispositivo é enviado.
  2. Os arquivos no APEX são acessados pelo caminho /apex/<apex_name>/.
  3. Quando uma versão atualizada do APEX é instalada em /data/apex, o caminho aponta para o novo APEX após a reinicialização.

Atualizar um serviço com um APEX

Para atualizar um serviço usando um APEX:

  1. Marque o serviço na partição do sistema como atualizável. Adicione a opção updatable à definição do serviço.

    /system/etc/init/myservice.rc:
    
    service myservice /system/bin/myservice
        class core
        user system
        ...
        updatable
    
  2. Crie um novo arquivo .rc para o serviço atualizado. Use a opção override para redefinir o serviço atual.

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

As definições de serviço só podem ser definidas no arquivo .rc de um APEX. Gatilhos de ação não têm suporte em APEXs.

Se um serviço marcado como atualizável for iniciado antes que os APEXs sejam ativados, o início será adiado até a conclusão da ativação dos APEXs.

Configurar o sistema para oferecer suporte a atualizações APEX

Defina a propriedade do sistema a seguir como true para oferecer suporte a atualizações de arquivos APEX.

<device.mk>:

PRODUCT_PROPERTY_OVERRIDES += ro.apex.updatable=true

BoardConfig.mk:
TARGET_FLATTEN_APEX := false

ou apenas

<device.mk>:

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

APEX nivelado

Para dispositivos legados, às vezes é impossível ou inviável atualizar o kernel antigo para oferecer suporte total ao APEX. Por exemplo, o kernel pode ter sido criado sem CONFIG_BLK_DEV_LOOP=Y, o que é crucial para ativar a imagem do sistema de arquivos dentro de um APEX.

O APEX nivelado é um APEX especialmente criado que pode ser ativado em dispositivos com um kernel legado. Os arquivos em um APEX nivelado são instalados diretamente em um diretório na partição integrada. Por exemplo, lib/libFoo.so em um APEX nivelado my.apex está instalado em /system/apex/my.apex/lib/libFoo.so.

A ativação de um APEX nivelado não envolve o dispositivo de loop. Todo o diretório /system/apex/my.apex é vinculado diretamente a /apex/name@ver.

As APEXs achatadas não podem ser atualizadas fazendo o download de versões atualizadas das APEXs da rede, porque as APEXs baixadas não podem ser achatadas. As APEXs planas só podem ser atualizadas por uma OTA normal.

O APEX nivelado é a configuração padrão. Isso significa que todos os APEXs são nivelados por padrão, a menos que você configure explicitamente seu dispositivo para criar APEXs não nivelados e oferecer suporte a atualizações APEX (como explicado acima).

Não é possível misturar APEXes achatados e não achatados em um dispositivo. Os APEXes em um dispositivo precisam estar todos não achatados ou todos achatados. Isso é especialmente importante ao enviar pré-builds APEX pré-assinados para projetos como o Mainline. Os APEXs que não são pré-assinados (ou seja, criados a partir da origem) também precisam ser não nivelados e assinados com as chaves adequadas. O dispositivo precisa herdar de updatable_apex.mk, conforme explicado em Como atualizar um serviço com um APEX.

APEXs compactados

O Android 12 e versões mais recentes incluem a compactação APEX para reduzir o impacto no armazenamento de pacotes APEX atualizáveis. Depois que uma atualização para um APEX é instalada, embora a versão pré-instalada não seja mais usada, ela ainda ocupa a mesma quantidade de espaço. O espaço ocupado permanece indisponível.

A compactação APEX minimiza esse impacto no armazenamento usando um conjunto altamente compactado de arquivos APEX em partições somente leitura (como a partição /system). O Android 12 e versões mais recentes usam um algoritmo de compactação ZIP DEFLATE.

A compactação não otimiza o seguinte:

  • Inicializar APEXs que precisam ser montados muito no início da sequência de inicialização.

  • APEXes não atualizáveis. A compactação só é benéfica se uma versão atualizada de um APEX for instalada na partição /data. Uma lista completa de APEXes atualizáveis está disponível na página Componentes do sistema modular.

  • APEXs de bibliotecas compartilhadas dinâmicas. Como apexd sempre ativa as duas versões de APEX (pré-instaladas e atualizadas), a compactação delas não agrega valor.

Formato de arquivo APEX compactado

Esse é o formato de um arquivo APEX compactado.

O diagrama mostra o formato de um arquivo APEX compactado

Figura 2. Formato de arquivo APEX compactado

No nível superior, um arquivo APEX compactado é um arquivo ZIP que contém o arquivo apex original em forma desinflada com um nível de compactação de 9 e com outros arquivos armazenados sem compactação.

Quatro arquivos compreendem um arquivo APEX:

  • original_apex: esvaziado com o nível de compactação de 9. Este é o arquivo APEX original e descompactado.
  • apex_manifest.pb: somente armazenados
  • AndroidManifest.xml: somente armazenados
  • apex_pubkey: somente armazenados

Os arquivos apex_manifest.pb, AndroidManifest.xml e apex_pubkey são cópias dos arquivos correspondentes em original_apex.

Criar APEX compactado

O APEX compactado pode ser criado usando a ferramenta apex_compression_tool.py, localizada em system/apex/tools.

Vários parâmetros relacionados à compactação APEX estão disponíveis no sistema de build.

Em Android.bp, a compressão de um arquivo APEX é controlada pela propriedade compressible:

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

Uma flag de produto PRODUCT_COMPRESSED_APEX controla se uma imagem do sistema criada a partir da origem precisa conter arquivos APEX compactados.

Para experimentação local, você pode forçar um build a compactar APEXs definindo OVERRIDE_PRODUCT_COMPRESSED_APEX= como true.

Os arquivos APEX compactados gerados pelo sistema de build têm a extensão .capex. A extensão facilita a distinção entre versões compactadas e não compactadas de um arquivo APEX.

Algoritmos de compactação com suporte

O Android 12 só oferece suporte à compactação deflate-zip.

Ativar um arquivo APEX compactado durante a inicialização

Antes que um APEX compactado possa ser ativado, o arquivo original_apex dentro dele é descompactado no diretório /data/apex/decompressed. O arquivo APEX descompactado resultante é vinculado ao diretório /data/apex/active.

Confira o exemplo a seguir como uma ilustração do processo descrito acima.

Considere /system/apex/com.android.foo.capex como um APEX compactado sendo ativado, com o versionCode 37.

  1. O arquivo original_apex dentro de /system/apex/com.android.foo.capex é descompactado em /data/apex/decompressed/com.android.foo@37.apex.
  2. restorecon /data/apex/decompressed/com.android.foo@37.apex é executado para verificar se tem um rótulo SELinux correto.
  3. Verificações são realizadas em /data/apex/decompressed/com.android.foo@37.apex para garantir a validade: apexd confere a chave pública agrupada em /data/apex/decompressed/com.android.foo@37.apex para conferir se ela é igual àquela agrupada em /system/apex/com.android.foo.capex.
  4. O arquivo /data/apex/decompressed/com.android.foo@37.apex está vinculado ao diretório /data/apex/active/com.android.foo@37.apex.
  5. A lógica de ativação normal para arquivos APEX descompactados é executada em /data/apex/active/com.android.foo@37.apex.

Interação com OTA

Os arquivos APEX compactados têm implicações na entrega e no aplicativo OTA. Como uma atualização OTA pode conter um arquivo APEX compactado com um nível de versão mais alto do que o ativo em um dispositivo, é necessário reservar uma certa quantidade de espaço livre antes que um dispositivo seja reinicializado para aplicar uma atualização OTA.

Para oferecer suporte ao sistema OTA, o apexd expõe estas duas APIs de vinculação:

  • calculateSizeForCompressedApex: calcula o tamanho necessário para descompactar arquivos APEX em um pacote OTA. Isso pode ser usado para verificar se um dispositivo tem espaço suficiente antes do download de uma OTA.
  • reserveSpaceForCompressedApex: reserva espaço no disco para uso futuro pelo apexd para descompactar arquivos APEX compactados dentro do pacote OTA.

No caso de uma atualização OTA A/B, o apexd tenta fazer a descompressão em segundo plano como parte da rotina OTA pós-instalação. Se a descompactação falhar, apexd vai realizar a descompactação durante a inicialização que aplica a atualização OTA.

Alternativas consideradas no desenvolvimento de APEX

Confira algumas opções que o AOSP considerou ao projetar o formato de arquivo APEX e por que elas foram incluídas ou excluídas.

Sistemas regulares de gerenciamento de pacotes

As distribuições do Linux têm sistemas de gerenciamento de pacotes, como dpkg e rpm, que são poderosos, maduros e robustos. No entanto, eles não foram adotados para o APEX porque não podem proteger os pacotes após a instalação. A verificação é realizada apenas quando os pacotes estão sendo instalados. Os invasores podem quebrar a integridade dos pacotes instalados sem serem notados. Essa é uma regressão para Android em que todos os componentes do sistema foram armazenados em sistemas de arquivos somente leitura, e a integridade deles é protegida por dm-verity para cada E/S. Qualquer adulteração em componentes do sistema precisa ser proibida ou detectável para que o dispositivo possa se recusar a inicializar se for comprometido.

dm-crypt para integridade

Os arquivos em um contêiner APEX são de partições integradas (por exemplo, a /system) protegidas por dm-verity, em que qualquer modificação nos arquivos é proibida, mesmo após a montagem das partições. Para fornecer o mesmo nível de segurança aos arquivos, todos os arquivos em um APEX são armazenados em uma imagem do sistema de arquivos pareada com uma árvore de hash e um descritor vbmeta. Sem o dm-verity, um APEX na partição /data fica vulnerável a modificações involuntárias feitas após a verificação e instalação.

Na verdade, a partição /data também é protegida por camadas de criptografia, como dm-crypt. Embora isso ofereça algum nível de proteção contra adulteração, o objetivo principal é a privacidade, não a integridade. Quando um invasor consegue acesso à partição /data, não há mais proteção, e isso é uma regressão em comparação com todos os componentes do sistema na partição /system. A árvore de hash dentro de um arquivo APEX, junto com o dm-verity, fornece o mesmo nível de proteção de conteúdo.

Redirecionar caminhos de /system para /apex

Arquivos de componentes do sistema empacotados em um APEX podem ser acessados por novos caminhos, como /apex/<name>/lib/libfoo.so. Quando os arquivos faziam parte da partição /system, eles eram acessíveis por caminhos como /system/lib/libfoo.so. Um cliente de um arquivo APEX (outros arquivos APEX ou a plataforma) precisa usar os novos caminhos. Talvez seja necessário atualizar o código existente como resultado da mudança do caminho.

Embora uma maneira de evitar a mudança de caminho seja sobrepor o conteúdo de um arquivo APEX na partição /system, a equipe do Android decidiu não sobrepor arquivos na partição /system porque isso poderia afetar o desempenho, já que o número de arquivos sobrepostos (possivelmente até mesmo empilhados um após o outro) aumentava.

Outra opção era sequestrar funções de acesso a arquivos, como open, stat e readlink, para que caminhos que começavam com /system fossem redirecionados para os caminhos correspondentes em /apex. A equipe do Android descartou essa opção porque é inviável mudar todas as funções que aceitam caminhos. Por exemplo, alguns apps vinculam estaticamente o Bionic, que implementa as funções. Nesses casos, esses apps não são redirecionados.