Endereço Sanitizer

AddressSanitizer (ASan) é uma ferramenta rápida baseada em compilador para detectar erros de memória no código nativo.

ASan detecta:

  • Estouro/subfluxo de buffer de pilha e heap
  • Uso de pilha depois de gratuito
  • Uso de pilha fora do escopo
  • Duplo livre/livre livre

O ASan é executado em ARM de 32 e 64 bits, além de x86 e x86-64. A sobrecarga de CPU do ASan é aproximadamente 2x, a sobrecarga de tamanho de código está entre 50% e 2x e uma grande sobrecarga de memória (dependente de seus padrões de alocação, mas na ordem de 2x).

O Android 10 e o branch master AOSP no AArch64 suportam ASan acelerado por hardware (HWASan) , uma ferramenta semelhante com menor sobrecarga de RAM e uma variedade maior de bugs detectados. O HWASan detecta o uso da pilha após o retorno, além dos bugs detectados pelo ASan.

O HWASan tem uma sobrecarga de CPU e tamanho de código semelhante, mas uma sobrecarga de RAM muito menor (15%). HWASan é não determinístico. Existem apenas 256 valores de tag possíveis, portanto, há uma probabilidade fixa de 0,4% de perder algum bug. O HWASan não possui as zonas vermelhas de tamanho limitado do ASan para detectar estouros e a quarentena de capacidade limitada para detectar o uso após a liberação, portanto, não importa para o HWASan o tamanho do estouro ou há quanto tempo a memória foi desalocada. Isso torna o HWASan melhor que o ASan. Você pode ler mais sobre o design do HWASan ou sobre o uso do HWASan no Android .

O ASan detecta estouros de pilha/global, além de estouros de heap, e é rápido com sobrecarga mínima de memória.

Este documento descreve como construir e executar partes/todo o Android com ASan. Se você estiver criando um aplicativo SDK/NDK com ASan, consulte Address Sanitizer .

Sanitizando executáveis ​​individuais com ASan

Adicione LOCAL_SANITIZE:=address ou sanitize: { address: true } à regra de compilação do executável. Você pode pesquisar o código para exemplos existentes ou para encontrar outros sanitizantes disponíveis.

Quando um bug é detectado, o ASan imprime um relatório detalhado tanto na saída padrão quanto no logcat e, em seguida, trava o processo.

Higienizando bibliotecas compartilhadas com ASan

Devido à forma como o ASan funciona, uma biblioteca construída com ASan só pode ser usada por um executável construído com ASan.

Para limpar uma biblioteca compartilhada usada em vários executáveis, nem todos criados com ASan, você precisa de duas cópias da biblioteca. A maneira recomendada de fazer isso é adicionar o seguinte ao Android.mk para o módulo em questão:

LOCAL_SANITIZE:=address
LOCAL_MODULE_RELATIVE_PATH := asan

Isso coloca a biblioteca em /system/lib/asan em vez de /system/lib . Em seguida, execute seu executável com:

LD_LIBRARY_PATH=/system/lib/asan

Para daemons do sistema, adicione o seguinte à seção apropriada de /init.rc ou /init.$device$.rc .

setenv LD_LIBRARY_PATH /system/lib/asan

Verifique se o processo está usando bibliotecas de /system/lib/asan quando presentes lendo /proc/$PID/maps . Se não for, pode ser necessário desabilitar o SELinux:

adb root
adb shell setenforce 0
# restart the process with adb shell kill $PID
# if it is a system service, or may be adb shell stop; adb shell start.

Melhores rastreamentos de pilha

O ASan usa um desbobinador rápido baseado em ponteiro de quadro para registrar um rastreamento de pilha para cada evento de alocação e desalocação de memória no programa. A maior parte do Android é construída sem ponteiros de quadro. Como resultado, muitas vezes você obtém apenas um ou dois quadros significativos. Para corrigir isso, reconstrua a biblioteca com ASan (recomendado!) ou com:

LOCAL_CFLAGS:=-fno-omit-frame-pointer
LOCAL_ARM_MODE:=arm

Ou defina ASAN_OPTIONS=fast_unwind_on_malloc=0 no ambiente do processo. O último pode ser muito intensivo em CPU, dependendo da carga.

Simbolização

Inicialmente, os relatórios ASan contêm referências a deslocamentos em binários e bibliotecas compartilhadas. Existem duas maneiras de obter o arquivo de origem e as informações de linha:

  • Certifique-se de que o binário llvm-symbolizer esteja presente em /system/bin . llvm-symbolizer é construído a partir de fontes em third_party/llvm/tools/llvm-symbolizer .
  • Filtre o relatório por meio do script external/compiler-rt/lib/asan/scripts/symbolize.py .

A segunda abordagem pode fornecer mais dados (ou seja, localizações de file:line ) devido à disponibilidade de bibliotecas simbolizadas no host.

ASan em aplicativos

O ASan não pode ver o código Java, mas pode detectar bugs nas bibliotecas JNI. Para isso, você precisa construir o executável com ASan, que neste caso é /system/bin/app_process( 32|64 ) . Isso habilita o ASan em todos os aplicativos do dispositivo ao mesmo tempo, o que é uma carga pesada, mas um dispositivo com 2 GB de RAM deve ser capaz de lidar com isso.

Adicione LOCAL_SANITIZE:=address à regra de compilação app_process em frameworks/base/cmds/app_process . Ignore o destino app_process__asan no mesmo arquivo por enquanto (se ainda estiver lá no momento em que você ler isso).

Edite a seção service zygote do arquivo system/core/rootdir/init.zygote( 32|64 ).rc apropriado para adicionar as seguintes linhas ao bloco de linhas recuadas contendo class main , também recuada pela mesma quantidade:

    setenv LD_LIBRARY_PATH /system/lib/asan:/system/lib
    setenv ASAN_OPTIONS allow_user_segv_handler=true

Compilação, sincronização adb, inicialização flash fastboot e reinicialização.

Usando a propriedade wrap

A abordagem da seção anterior coloca o ASan em todos os aplicativos do sistema (na verdade, em todos os descendentes do processo Zygote). É possível executar apenas um (ou vários) aplicativos com ASan, trocando alguma sobrecarga de memória por uma inicialização mais lenta do aplicativo.

Isso pode ser feito iniciando seu aplicativo com o wrap. propriedade. O exemplo a seguir executa o aplicativo Gmail em ASan:

adb root
adb shell setenforce 0  # disable SELinux
adb shell setprop wrap.com.google.android.gm "asanwrapper"

Nesse contexto, asanwrapper reescreve /system/bin/app_process para /system/bin/asan/app_process , que é construído com ASan. Ele também adiciona /system/lib/asan no início do caminho de pesquisa da biblioteca dinâmica. Desta forma, as bibliotecas instrumentadas ASan de /system/lib/asan são preferidas às bibliotecas normais em /system/lib quando executadas com asanwrapper .

Se um bug for encontrado, o aplicativo trava e o relatório é impresso no log.

SANITIZE_TARGET

O Android 7.0 e superior inclui suporte para construir toda a plataforma Android com ASan de uma só vez. (Se você estiver criando uma versão superior ao Android 9, HWASan é uma escolha melhor.)

Execute os comandos a seguir na mesma árvore de compilação.

make -j42
SANITIZE_TARGET=address make -j42

Nesse modo, userdata.img contém bibliotecas extras e também deve ser atualizado para o dispositivo. Use a seguinte linha de comando:

fastboot flash userdata && fastboot flashall

Isso cria dois conjuntos de bibliotecas compartilhadas: normal em /system/lib (o primeiro faz invocação) e ASan-instrumentado em /data/asan/lib (o segundo faz invocação). Os executáveis ​​da segunda compilação substituem os da primeira compilação. Os executáveis ​​instrumentados ASan obtêm um caminho de pesquisa de biblioteca diferente que inclui /data/asan/lib antes /system/lib através do uso de /system/bin/linker_asan em PT_INTERP .

O sistema de compilação sobrepõe os diretórios de objetos intermediários quando o valor $SANITIZE_TARGET é alterado. Isso força uma reconstrução de todos os alvos enquanto preserva os binários instalados em /system/lib .

Alguns destinos não podem ser criados com ASan:

  • Executáveis ​​vinculados estaticamente
  • LOCAL_CLANG:=false
  • LOCAL_SANITIZE:=false não é ASan'd para SANITIZE_TARGET=address

Executáveis ​​como esses são ignorados na compilação SANITIZE_TARGET , e a versão da primeira invocação do make é deixada em /system/bin .

Bibliotecas como esta são construídas sem ASan. Eles podem conter algum código ASan das bibliotecas estáticas das quais dependem.

Documentação de suporte