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 mais baixo. Esse formato facilita as atualizações de componentes do sistema que não se encaixam no modelo padrão de aplicativos Android. Alguns exemplos de componentes são serviços e bibliotecas nativas, camadas de abstração de hardware (HALs), tempo 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 padrão de apps (por exemplo, serviços, atividades) por apps instaladores de pacotes (como o app Google Play Store), usar um modelo semelhante para componentes de SO de nível mais baixo tem as seguintes desvantagens:

  • Não é possível usar módulos baseados em APK 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 etapa posterior do procedimento de inicialização.
  • O formato APK (principalmente o manifesto) foi criado 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 do gerenciador APEX, que é um serviço que gerencia arquivos APEX.

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

Formato APEX

Este é 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 e a versão do pacote, 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 infraestrutura relacionadas a APKs, como ADB, PackageManager e apps instaladores de pacotes (como a Play Store). Por exemplo, o arquivo APEX pode usar uma ferramenta atual, 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.

O apex_manifest.json é recomendado em vez do AndroidManifest.xml para novos códigos e sistemas que lidam com APEX. 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 tempo de 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 montável no lugar. Os arquivos regulares são incluídos no arquivo apex_payload.img.

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

Diretrizes de nomenclatura do 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 dessa empresa.
  • com.<companyname>.<devicename>.*
    • Reservado para APEXes exclusivos de um dispositivo específico (ou subconjunto de dispositivos).

Gerente do APEX

O gerenciador do 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 do APEX usa esses pacotes por padrão se não houver atualizações disponíveis.

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

  1. Um arquivo APEX é baixado por um app instalador de pacotes, ADB ou 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 de APEX.
  3. O gerenciador do 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 da instalação recebe uma transmissão após a verificação do pacote.
  6. Para continuar a instalação, é necessário reiniciar o sistema.
  7. Na próxima inicialização, o gerenciador do 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 do mapeador de dispositivos sobre o dispositivo de loopback.
    4. Monta o dispositivo de bloco do mapeador de dispositivos 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 vinculador para que outros componentes do sistema consultem 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 está 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 dos arquivos APK, como um app instalador de pacotes, o utilitário de assinatura e o gerenciador de pacotes.

O arquivo AndroidManifest.xml dentro de um arquivo APEX é mínimo, consistindo no pacote name, versionCode e targetSdkVersion, minSdkVersion, e maxSdkVersion opcionais para segmentação refinada. Essas informações permitem que os arquivos APEX sejam entregues por canais atuais, como apps instaladores de pacotes e ADB.

Tipos de arquivos aceitos

O formato APEX é compatível com estes tipos de arquivo:

  • Libs 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 possibilidade de atualizar um tipo de arquivo depende da plataforma e da estabilidade das definições das interfaces para os tipos de arquivos.

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 nesse processo.

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

APEX em partições integradas

Os arquivos APEX podem estar em partições integradas, como /system. A partição já está no 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 e um código de versão maior ou igual. O novo APEX é armazenado em /data e, assim como os APKs, a versão recém-instalada substitui a versão já presente na partição integrada. Mas, 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 principais do 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 alcançar um bom desempenho do sistema ao usar módulos APEX.

Versões compatíveis do kernel

Os módulos principais do APEX são compatíveis com dispositivos que usam versões do kernel 4.4 ou mais recentes. 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 obrigatórios

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

Versão do kernel 4.4

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

  • 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)
  • Reverter "ANDROID: dm verity: add minimum prefetch size" (4.4)
  • UPSTREAM: loop: drop caches if offset or block_size are changed (4.4)

Versões 4.9/4.14/4.19 do kernel

Para receber os patches necessários para as versões do kernel 4.9/4.14/4.19, faça o down-merge da ramificação android-common.

Opções de configuração do 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âmetros da linha de comando do kernel

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

  • loop.max_loop NÃO pode ser definido
  • loop.max_part precisa ser <= 8

Criar um APEX

Esta seção descreve como criar um APEX usando o sistema de build do Android. Confira abaixo 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 e locais de arquivos no APEX

Tipo de arquivo Localização no APEX
Bibliotecas compartilhadas /lib e /lib64 (/lib/arm para arm 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 for listado na propriedade native_shared_libs.

Processar várias ABIs

Instale a propriedade native_shared_libs para as interfaces binárias do aplicativo (ABIs) primárias e secundárias do dispositivo. Se um APEX for destinado a dispositivos com uma única ABI (ou seja, apenas 32 bits ou apenas 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 apenas de 32 bits, somente a variante de 32 bits do binário será instalada.
  • Se o dispositivo for apenas de 64 bits, somente a variante de 64 bits do binário será instalada.

Para adicionar controle refinado sobre as ABIs das bibliotecas e binários nativos, 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, se compatível.
  • prefer32: corresponde à ABI de 32 bits do dispositivo, se compatível. Se a ABI de 32 bits não for compatível, ela vai corresponder à ABI de 64 bits.
  • both: corresponde às duas ABIs. Esse é o padrão para native_shared_libraries.

As propriedades java, libraries e prebuilts são independentes de 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/privada e faça 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 é gravado no APEX. Em tempo de execução, o apexd verifica o APEX usando uma chave pública com o mesmo ID no dispositivo.

Assinatura APEX

Assine os APEXs da mesma forma que os APKs. Assine os APEXs duas vezes: uma para o mini sistema 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 vai usar build/target/product/security/testkey por padrão.
  • <name>: o APEX é assinado com o certificado <name> no mesmo diretório de 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 forma:
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 no momento não estiver em uso (por exemplo, todos os serviços que ele contém foram interrompidos), um novo APEX poderá ser instalado sem 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 montagem, aquele que corresponde à versão mais recente é vinculado em /apex/<apex_name>.

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

Os APEXes costumam ser 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 no /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. Os gatilhos de ação não são compatíveis com APEXes.

Se um serviço marcado como atualizável for iniciado antes da ativação dos APEXes, o início será adiado até que a ativação dos APEXes seja concluída.

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

Defina a seguinte propriedade do sistema 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

Em 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, que é essencial para montar a imagem do sistema de arquivos em um APEX.

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

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

Não é possível atualizar APEXs simplificados baixando versões atualizadas deles da rede porque os APEXs baixados não podem ser simplificados. Os APEXs simplificados só podem ser atualizados por uma OTA regular.

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

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

APEXs compactados

O Android 12 e versões mais recentes têm 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 DEFLATE zip.

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

  • Inicializa os APEXes que precisam ser montados 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 dos APEXs atualizáveis está disponível na página Componentes modulares do sistema.

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

Formato de arquivo APEX compactado

Este é 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 de forma descompactada com um nível de compactação de 9 e com outros arquivos armazenados sem compactação.

Um arquivo APEX é composto por quatro arquivos:

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

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 do APEX estão disponíveis no sistema de build.

Em Android.bp, a compressibilidade 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 da origem precisa conter arquivos APEX compactados.

Para testes locais, 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 compatíveis

O Android 12 só é compatível com a 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 de forma rígida ao diretório /data/apex/active.

Considere 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 versionCode 37.

  1. O arquivo original_apex em /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 ele tem um rótulo do SELinux correto.
  3. As verificações são realizadas em /data/apex/decompressed/com.android.foo@37.apex para garantir a validade dele: apexd verifica se a chave pública agrupada em /data/apex/decompressed/com.android.foo@37.apex é igual à agrupada em /system/apex/com.android.foo.capex.
  4. O arquivo /data/apex/decompressed/com.android.foo@37.apex tem um link físico com o diretório /data/apex/active/com.android.foo@37.apex.
  5. A lógica de ativação regular para arquivos APEX não compactados é realizada em /data/apex/active/com.android.foo@37.apex.

Interação com OTA

Arquivos APEX compactados têm implicações na entrega e aplicação de OTA. Como uma atualização OTA pode conter um arquivo APEX compactado com um nível de versão maior do que o ativo em um dispositivo, uma certa quantidade de espaço livre precisa ser reservada 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 binder:

  • 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 descompactação em segundo plano como parte da rotina OTA pós-instalação. Se a descompactação falhar, o apexd vai realizar a descompactação durante a inicialização que aplica a atualização OTA.

Alternativas consideradas ao desenvolver o APEX

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

Sistemas de gerenciamento de pacotes comuns

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 violar a integridade dos pacotes instalados sem serem notados. Essa é uma regressão para o Android, em que todos os componentes do sistema eram armazenados em sistemas de arquivos somente leitura, cuja integridade é protegida pelo dm-verity para cada E/S. Qualquer violação dos componentes do sistema precisa ser proibida ou detectável para que o dispositivo se recuse a inicializar se estiver comprometido.

dm-crypt para integridade

Os arquivos em um contêiner APEX são de partições integradas (por exemplo, a partição /system) protegidas pelo dm-verity, em que qualquer modificação nos arquivos é proibida, mesmo depois que as partições são montadas. Para oferecer 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 indesejadas feitas depois da 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 ganha acesso à partição /data, não há mais proteção. Isso é uma regressão em comparação com todos os componentes do sistema na partição /system. A árvore de hash em um arquivo APEX com dm-verity oferece o mesmo nível de proteção de conteúdo.

Redirecionar caminhos de /system para /apex

Os 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 podiam ser acessados 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 atual como resultado da mudança de caminho.

Uma maneira de evitar a mudança de caminho é sobrepor o conteúdo do arquivo em um arquivo APEX na partição /system. No entanto, a equipe do Android decidiu não sobrepor arquivos na partição /system porque isso pode afetar o desempenho à medida que o número de arquivos sobrepostos (possivelmente até mesmo empilhados um após o outro) aumenta.

Outra opção era sequestrar funções de acesso a arquivos, como open, stat e readlink, para que os caminhos que começam 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, os apps não são redirecionados.