Alterações do Android para desenvolvedores NDK

julho 18, 2016 6:00 pm Publicado por Deixe um comentário

Relacionada com outras melhorias na plataforma Android, o vinculo dinâmico (dynamic linker) no Android M e N tem requisitos mais rigorosos para escrever código multiplataforma, limpo e compatível com o nativo, a fim de carregar. É necessário que o código nativo de um aplicativo siga as regras e as recomendações para assegurar uma transição suave para as versões mais recentes do Android.

Abaixo, descrevemos detalhadamente cada mudança individual relacionada com o carregamento do código nativo, as consequências e os passos que você pode tomar para evitar problemas.

Ferramentas necessárias: há um <arch>-linux-android-readelf binário (por exemplo, arm-linux-androideabi-readelf ou i686-linux-android-readelf) para cada arquitetura no NDK (sob toolchains/), mas você pode usar readelf para qualquer arquitetura, como iremos fazer usando somente a inspeção básica. No Linux, você precisa ter o pacote “binutils” instalado para readelf, e “pax-utils” para scanelf.

API privada (em vigor desde a API 24)

Bibliotecas nativas devem usar somente API pública, e não devem vincular com as bibliotecas de plataforma não-NDK. Começando com a API 24, essa regra é reforçada, e as aplicações não são mais capazes de carregar bibliotecas de plataforma não-NDK. A regra é imposta pelo vinculador dinâmico, de modo que as bibliotecas não-públicas não são acessíveis, independentemente da forma como o código tenta carregá-los: System.loadLibrary(…), DT_NEEDED e chamadas diretas para dlopen(…) irão falhar exatamente da mesma maneira.

Os usuários devem ter uma experiência de aplicação consistente nas atualizações, e os desenvolvedores não devem ter que fazer atualizações de emergência nos apps para lidar com mudanças de plataforma. Por essa razão, recomendamos não usar os símbolos privados do C/C++. Símbolos privados não são testados como parte da Suíte de Teste de Compatibilidade (CTS), pela qual todos os dispositivos Android devem passar. Eles podem não existir, ou podem se comportar de forma diferente. Isso torna os aplicativos que os usam os mais propensos a falhar em dispositivos específicos, ou em versões futuras – como muitos desenvolvedores descobriram quando o Android 6.0 Marshmallow mudou do OpenSSL para o BoringSSL.

A fim de reduzir o impacto no usuário nessa transição, nós identificamos um conjunto de bibliotecas que tem um uso significativo nos aplicativos mais instalados da Google Play, e que são viáveis para nós suportamos em curto prazo (incluindo libandroid_runtime.so, libcutils.so, libcrypto.so e libssl.so). Para te dar mais tempo para fazer a transição, vamos suportar temporariamente essas bibliotecas; então, se você vir um aviso que significa que seu código não vai funcionar em uma versão futura – por favor, corrija-o agora!

$ readelf --dynamic libBroken.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libnativehelper.so]
 0x00000001 (NEEDED)                     Shared library: [libutils.so]
 0x00000001 (NEEDED)                     Shared library: [libstagefright_foundation.so]
 0x00000001 (NEEDED)                     Shared library: [libmedia_jni.so]
 0x00000001 (NEEDED)                     Shared library: [liblog.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
 0x00000001 (NEEDED)                     Shared library: [libz.so]
 0x00000001 (NEEDED)                     Shared library: [libstdc++.so]
 0x00000001 (NEEDED)                     Shared library: [libm.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so]

Potenciais problemas: a partir da API 24, o vinculador dinâmico não irá carregar bibliotecas privadas, impedindo a aplicação de carregar.

Resolução: reescrever o código nativo para contar apenas com a API pública. Como uma solução de curto prazo, bibliotecas da plataforma sem dependências complexas (libcutils.so) podem ser copiadas para o projeto. Como uma solução de longo prazo, o código relevante deve ser copiado para a árvore do projeto. SSL/Media/JNI internal/binder não devem ser acessados a partir do código nativo. Quando necessário, o código nativo deve chamar métodos adequados da API Java pública.

Uma lista completa de bibliotecas públicas está disponível dentro do NDK, sob a pasta plataforms/android-API/usr/lib.

Nota: SSL/crypto é um caso especial, os aplicativos NÃO devem usar as bibliotecas da plataforma libcrypto e libssl diretamente, mesmo em plataformas mais antigas. Todas as aplicações devem usar o provedor de segurança GMS para garantir que eles estejam protegidos de vulnerabilidades conhecidas.

Faltando Cabeçalhos de sessão – Section Headers – (em vigor desde a API 24)

Cada arquivo ELF tem informações adicionais contidas nos cabeçalhos de sessão. Esses cabeçalhos devem estar presentes agora, porque o vinculador dinâmico os usa para verificar a estabilidade. Alguns desenvolvedores tentam tirá-los, em uma tentativa de ofuscar o binário e impedir a engenharia reversa (isso não ajuda, porque é possível reconstruir a informação retirada usando ferramentas amplamente disponíveis).

$ readelf --header libBroken.so | grep 'section headers'
  Start of section headers:          0 (bytes into file)
  Size of section headers:           0 (bytes)
  Number of section headers:         0
$

Solução: remover os passos adicionais de sua build que remove os cabeçalhos de sessão.

Relocações de Texto – Text Relocations (em vigor desde a API 23)

Começando com a API 23, objetos compartilhados não devem conter relocações de texto. Isto é, o código deve ser carregado como é e não deve ser modificado. Tal abordagem reduz o tempo de carregamento e melhora a segurança.

A razão usual para relocação de texto é o não-posicionamento independente da escrita assembler ser escrita a mão. Isso não é comum. Use a ferramenta scanelf como descrito em nossa documentação para obter mais diagnósticos:

$ scanelf -qT libTextRel.so
  libTextRel.so: (memory/data?) [0x15E0E2] in (optimized out: previous simd_broken_op1) [0x15E0E0]
  libTextRel.so: (memory/data?) [0x15E3B2] in (optimized out: previous simd_broken_op2) [0x15E3B0]
[skipped the rest]

Se você não tem nenhuma ferramenta scanelf disponível, é possível fazer uma verificação básica com readelf, procure por qualquer entrada TEXTREL ou uma flag TEXTREL. Qualquer uma é suficiente (o valor correspondente à entrada TEXTREL é irrelevante e tipicamente é 0 — a simples presença da entrada TEXTREL declara que o .so contém relocalizações de texto). Este exemplo tem dois indicadores presentes:

$ readelf --dynamic libTextRel.so | grep TEXTREL
 0x00000016 (TEXTREL)                    0x0
 0x0000001e (FLAGS)                      SYMBOLIC TEXTREL BIND_NOW
$

Nota: é tecnicamente possível ter um objeto compartilhado com entry/flag TEXTREL, mas sem quaisquer relocalizações de texto reais. Isso não acontece com o NDK, mas, se você está gerando arquivos ELF por si só, tenha certeza de que você não está gerando arquivos ELF que afirmam ter relocalizações de texto, porque o vinculador dinâmico do Android confia na entry/flag.

Problemas potenciais: Realocações forçam as páginas de código a ser graváveis e aumentam o número de páginas sujas na memória. O vinculador dinâmico tem emitido avisos sobre as relocalizações de texto desde o Android K (API 19), mas na API 23 e posteriores ele se recusa a carregar código com realocações de texto.

Resolução: reescreva o assembler para ter a posição independente para garantir que as relocalizações de texto não sejam necessárias. Verifique a documentação do Gentoo para as receitas de bolo.

Entradas DT_NEEDED inválidas (em vigor desde a API 23)

Enquanto dependências de biblioteca (entradas DT_NEEDED nos cabeçalhos ELF) podem ser caminhos absolutos, isso não faz sentido no Android porque você não tem controle sobre onde a biblioteca será instalada pelo sistema. Uma entrada DT_NEEDED deve ser a mesma da biblioteca necessária SONAME, deixando a função de encontrar a biblioteca em tempo de execução para o vinculador dinâmico.

Antes da API 23, o vinculador dinâmico do Android ignorava o caminho completo e utilizava apenas o nome base (a parte após o último ‘/’) quando procurava pelas bibliotecas necessárias. Desde a API 23, o vinculador de runtime irá honrar exatamente o que estiver na DT_NEEDED e por isso não será capaz de carregar a biblioteca se ela não estiver presente na localização exata no dispositivo.

Pior ainda, alguns sistemas de compilação têm bugs que fazem com que eles insiram entradas DT_NEEDED que apontam para um arquivo na build host, algo que não pode ser encontrado no dispositivo.

$ readelf --dynamic libSample.so | grep NEEDED
 0x00000001 (NEEDED)                     Shared library: [libm.so]
 0x00000001 (NEEDED)                     Shared library: [libc.so]
 0x00000001 (NEEDED)                     Shared library: [libdl.so]
 0x00000001 (NEEDED)                     Shared library:
[C:UsersbuildAndroidcijnilibBroken.so]
$

Problemas potenciais: antes da API 23, a entrada da DT_NEEDED foi usada para o nome base da entrada, mas a partir da API 23 o Android em tempo de execução irá tentar carregar a biblioteca usando o caminho especificado, e esse caminho não vai existir no dispositivo. Existem sistemas de terceiros toolchains/build que usam um caminho no build host em vez de SONAME.

Solução: certifique-se de que todas as bibliotecas necessárias estão referenciadas apenas por SONAME. É melhor deixar o vinculador de tempo de execução encontrar e carregar as bibliotecas, já que a localização pode mudar de dispositivo para dispositivo.

Faltando SONAME (usado desde a API 23)

Cada objeto ELF compartilhado (“biblioteca nativa”) deve ter um atributo SONAME (Shared Object Name). O conjunto de ferramentas NDK acrescenta esse atributo por padrão, então sua ausência indica um conjunto de ferramentas alternativa configurado incorretamente ou um erro de configuração em seu sistema de build. A falta de SONAME pode levar a problemas de tempo de execução, tais como o carregamento de biblioteca errada: o nome do arquivo é usado quando esse atributo de função está ausente.

$ readelf --dynamic libWithSoName.so | grep SONAME
 0x0000000e (SONAME)                     Library soname: [libWithSoName.so]
$

Problemas potenciais: conflitos de namespace podem fazer com que a biblioteca errada seja carregada em tempo de execução, o que leva a falhas quando os símbolos necessários não forem encontrados, ou tentar usar uma biblioteca ABI-incompatível, que não é a biblioteca que você estava esperando.

Resolução: o NDK atual gera o SONAME correto por padrão. Tenha certeza de estar usando o NDK atual e que você não tenha configurado o sistema de build para gerar entradas SONAME incorretas (usando a opção -soname do vinculador).

Por favor, lembre: código limpo e cross-plataforma construído com um NDK atual não deverá ter problemas com o Android N. Nós encorajamos você a rever sua build de código nativo para que ela produza binários corretos.

***

Este artigo é do Android Security Team. Ele foi escrito por Dmitry Malykhanov. A tradução foi feita pela Redação iMasters com autorização. Você pode conferir o original em: http://android-developers.blogspot.com.br/2016/06/android-changes-for-ndk-developers.html

Mensagem do anunciante:

Conheça a Umbler, startup de Cloud Hosting por demanda feita para agências e desenvolvedores. Experimente grátis!

Source: IMasters

Categorizados em:

Este artigo foi escrito pormajor

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *