GWP-ASan

O GWP-ASan é um recurso de alocação de memória nativo que ajuda a localizar bugs de uso após a liberação e de overflow do buffer de heap (links em inglês). O nome informal é um acrônimo recursivo, "GWP-ASan Will Provide Allocation SANity". Ao contrário de HWASan ou Depuração Malloc (links em inglês), o GWP-ASan não requer origem ou recompilação (ou seja, funciona com pré-compiladores) e funciona em processos de 32 bits. Este tópico descreve as ações que você precisa realizar para ativar esse recurso no seu app. O GWP-ASan está disponível em apps direcionados ao Android 11 (nível de API "R") ou mais recente.

Visão geral

O GWP-ASan é ativado em alguns aplicativos do sistema e executáveis de plataforma selecionados aleatoriamente na inicialização do processo (ou quando o zigoto se divide). Ative o GWP-ASan no seu próprio app para encontrar bugs relacionados à memória e preparar o app para a compatibilidade com a extensão ARM Memory Tagging Extension (MTE) (link em inglês). Os mecanismos de amostragem de alocação também oferecem confiabilidade em consultas de morte (link em inglês).

Uma vez ativado, o GWP-ASan intercepta um subconjunto de alocações de heap escolhido aleatoriamente e o coloca em uma região especial que captura bugs de corrupção de memória de heap difíceis de detectar. Com usuários suficientes, até mesmo essa baixa taxa de amostragem encontrará bugs de segurança de memória de heap que não são encontrados por meio de testes regulares. Por exemplo, o GWP-ASan encontrou um número significativo de bugs no navegador Chrome (muitos deles ainda estão sob visualização restrita).

O GWP-ASan coleta informações adicionais sobre todas as alocações que intercepta. Essas informações estão disponíveis quando o GWP-ASan detecta uma violação de segurança de memória e é colocado automaticamente no relatório de falhas nativas, o que pode ajudar significativamente na depuração (veja um exemplo).

O GWP-ASan foi projetado para não sobrecarregar significativamente a CPU. O GWP-ASan introduz uma sobrecarga pequena e fixa de RAM quando ativado. Essa sobrecarga é decidida pelo sistema Android e atualmente é de aproximadamente 70 kibibytes (KiB) para cada processo afetado.

Ativar no seu app

O GWP-ASan pode ser ativado por apps em um nível por processo usando a tag android:gwpAsanMode no manifesto do app. As opções a seguir são permitidas:

  • Sempre desativado (android:gwpAsanMode="never"): essa configuração desativa completamente o GWP-ASan no seu app e é o padrão para apps que não são do sistema.

  • Sempre ativado (android:gwpAsanMode="always"): essa configuração ativa o GWP-ASan no seu app, o que inclui o seguinte:

    1. O sistema operacional reserva uma quantidade fixa de RAM para operações do GWP-ASan, aproximadamente 70 KiB para cada processo afetado. Ative o GWP-ASan se o app não for extremamente sensível a aumentos no uso da memória.

    2. O GWP-ASan intercepta um subconjunto de alocações de heap escolhido aleatoriamente e o coloca em uma região especial que detecta violações de segurança da memória de maneira confiável.

    3. Quando ocorre uma violação de segurança de memória na região especial, o GWP-ASan encerra o processo.

    4. O GWP-ASan fornece mais informações no relatório de falhas.

Para ativar o GWP-ASan globalmente no seu app, adicione o seguinte ao arquivo AndroidManifest.xml:

<application android:gwpAsanMode="always">
  ...
</application>

Além disso, o GWP-ASan pode ser ativado ou desativado explicitamente para subprocessos específicos do app. É possível segmentar atividades e serviços usando processos explicitamente ativados ou desativados do GWP-ASan. Veja o exemplo a seguir:

<application>
  <processes>
    <!-- Create the (empty) application process -->
    <process />

    <!-- Create subprocesses with GWP-ASan both explicitly enabled and disabled. -->
    <process android:process=":gwp_asan_enabled"
               android:gwpAsanMode="always" />
    <process android:process=":gwp_asan_disabled"
               android:gwpAsanMode="never" />
  </processes>

  <!-- Target services and activities to be run on either the GWP-ASan enabled or disabled processes. -->
  <activity android:name="android.gwpasan.GwpAsanEnabledActivity"
            android:process=":gwp_asan_enabled" />
  <activity android:name="android.gwpasan.GwpAsanDisabledActivity"
            android:process=":gwp_asan_disabled" />
  <service android:name="android.gwpasan.GwpAsanEnabledService"
           android:process=":gwp_asan_enabled" />
  <service android:name="android.gwpasan.GwpAsanDisabledService"
           android:process=":gwp_asan_disabled" />
</application>

Suporte para desenvolvedores

Estas seções descrevem problemas que podem ocorrer ao usar o GWP-ASan e como resolvê-los.

Os traces de alocação/desalocação estão ausentes

Se você estiver diagnosticando uma falha nativa que parece estar sem frames de alocação/desalocação, seu aplicativo provavelmente não terá ponteiros de frame (link em inglês). O GWP-ASan usa ponteiros de frame para registrar os traces de alocação e desalocação por motivos de desempenho e não consegue desvincular o stack trace caso ele não esteja presente. Você precisará recompilar seu código com ponteiros de frame ativados (-fno-omit-frame-pointer) para conseguir stack traces de alocação e desalocação. Os ponteiros de frame são ativados por padrão para dispositivos arm64 e desativados por padrão para dispositivos arm32.

Reprodução de violações de segurança

O GWP-ASan foi projetado para capturar violações de segurança da memória de heap nos dispositivos dos usuários. O GWP-ASan fornece o máximo de contexto possível sobre a falha (trace de acesso da violação, string de causa e traces de alocação/desalocação), mas ainda pode ser difícil deduzir como a violação ocorreu. Infelizmente, como a detecção de bugs é probabilística, os relatórios do GWP-ASan costumam ser difíceis de reproduzir em um dispositivo local.

Nesses casos, se o bug afetar dispositivos de 64 bits, use HWAddressSanitizer (HWASan). O HWASan detecta violações de segurança da memória de maneira confiável na pilha, no heap e em globais. Executar seu aplicativo com o HWASan pode reproduzir de forma confiável o mesmo resultado que está sendo relatado pelo GWP-ASan.

Nos casos em que a execução do aplicativo no HWASan é insuficiente para determinar a causa raiz de um bug, tente confundir o código em questão. Você pode segmentar seus esforços de confusão com base nas informações do relatório do GWP-ASan, que podem detectar e revelar de maneira confiável problemas de integridade do código.

Exemplo

Este exemplo de código nativo tem um bug de heap de uso após a liberação:

#include <jni.h>
#include <string>
#include <string_view>

jstring native_get_string(JNIEnv* env) {
   std::string s = "Hellooooooooooooooo ";
   std::string_view sv = s + "World\n";

   // BUG: Use-after-free. `sv` holds a dangling reference to the ephemeral
   // string created by `s + "World\n"`. Accessing the data here is a
   // use-after-free.
   return env->NewStringUTF(sv.data());
}

extern "C" JNIEXPORT jstring JNICALL
Java_android11_test_gwpasan_MainActivity_nativeGetString(
    JNIEnv* env, jobject /* this */) {
  // Repeat the buggy code a few thousand times. GWP-ASan has a small chance
  // of detecting the use-after-free every time it happens. A single user who
  // triggers the use-after-free thousands of times will catch the bug once.
  // Alternatively, if a few thousand users each trigger the bug a single time,
  // you'll also get one report (this is the assumed model).
  jstring return_string;
  for (unsigned i = 0; i < 0x10000; ++i) {
    return_string = native_get_string(env);
  }

  return reinterpret_cast<jstring>(env->NewGlobalRef(return_string));
}

Para executar um teste usando o código de exemplo acima, o GWP-ASan detectou o uso ilegal e acionou o relatórios de erros abaixo. O GWP-ASan aprimorou o relatório automaticamente, fornecendo informações sobre o tipo de falha, os metadados de alocação e os stack traces de alocação e desalocação associados.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/sargo/sargo:10/RPP3.200320.009/6360804:userdebug/dev-keys'
Revision: 'PVT1.0'
ABI: 'arm64'
Timestamp: 2020-04-06 18:27:08-0700
pid: 16227, tid: 16227, name: 11.test.gwpasan  >>> android11.test.gwpasan <<<
uid: 10238
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x736ad4afe0
Cause: [GWP-ASan]: Use After Free on a 32-byte allocation at 0x736ad4afe0

backtrace:
      #00 pc 000000000037a090  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckNonHeapValue(char, art::(anonymous namespace)::JniValueType)+448)
      #01 pc 0000000000378440  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::CheckPossibleHeapValue(art::ScopedObjectAccess&, char, art::(anonymous namespace)::JniValueType)+204)
      #02 pc 0000000000377bec  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::ScopedCheck::Check(art::ScopedObjectAccess&, bool, char const*, art::(anonymous namespace)::JniValueType*)+612)
      #03 pc 000000000036dcf4  /apex/com.android.art/lib64/libart.so (art::(anonymous namespace)::CheckJNI::NewStringUTF(_JNIEnv*, char const*)+708)
      #04 pc 000000000000eda4  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (_JNIEnv::NewStringUTF(char const*)+40)
      #05 pc 000000000000eab8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+144)
      #06 pc 000000000000edf8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
      ...

deallocated by thread 16227:
      #00 pc 0000000000048970  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
      #01 pc 0000000000048f30  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::deallocate(void*)+184)
      #02 pc 000000000000f130  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::_DeallocateCaller::__do_call(void*)+20)
      ...
      #08 pc 000000000000ed6c  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char> >::~basic_string()+100)
      #09 pc 000000000000ea90  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+104)
      #10 pc 000000000000edf8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
      ...

allocated by thread 16227:
      #00 pc 0000000000048970  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::AllocationMetadata::CallSiteInfo::RecordBacktrace(unsigned long (*)(unsigned long*, unsigned long))+80)
      #01 pc 0000000000048e4c  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan::GuardedPoolAllocator::allocate(unsigned long)+368)
      #02 pc 000000000003b258  /apex/com.android.runtime/lib64/bionic/libc.so (gwp_asan_malloc(unsigned long)+132)
      #03 pc 000000000003bbec  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+76)
      #04 pc 0000000000010414  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (operator new(unsigned long)+24)
      ...
      #10 pc 000000000000ea6c  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (native_get_string(_JNIEnv*)+68)
      #11 pc 000000000000edf8  /data/app/android11.test.gwpasan/lib/arm64/libmy-test.so (Java_android11_test_gwpasan_MainActivity_nativeGetString+44)
      ...

Mais informações

Para saber mais sobre os detalhes de implementação do GWP-ASan, consulte a documentação do LLVM (link em inglês). Para saber mais sobre os relatórios de falhas nativas do Android, consulte Diagnóstico de falhas nativas.