GWP-ASan

GWP-ASan es una función de asignación de memoria nativa que ayuda a encontrar errores de uso después de liberación y desbordamiento del búfer del montón. Su nombre informal es un acrónimo recurrente en inglés, que indica "GWP-ASan Will Provide Allocation SANity" (GWP-ASan informará el estado de asignación). A diferencia de HWASan o ladepuración de malloc, GWP-ASan no requiere código fuente ni recompilación (es decir, funciona con compilaciones previas) y funciona en procesos de 32 bits. En este tema se describen las acciones que debes realizar para habilitar esta función en tu app. GWP-ASan está disponible en apps orientadas a Android 11 (API nivel "R") o versiones posteriores.

Descripción general

GWP-ASan está habilitado en algunas aplicaciones del sistema y en archivos ejecutables de la plataforma seleccionados al azar durante el inicio del proceso (o cuando Zygote se bifurca). Habilita GWP-ASan en tu propia app para ayudarte a encontrar errores relacionados con la memoria y preparar la app para la compatibilidad con extensiones de etiquetado de memoria (MTE) ARM. Los mecanismos de muestreo de asignación también proporcionan confiabilidad contra las consultas de fin de procesos.

Una vez habilitada, GWP-ASan intercepta un subconjunto de asignaciones de montón elegido al azar y las coloca en una región especial que capta errores de memoria del montón difíciles de detectar. Si hay suficientes usuarios, incluso esta baja tasa de muestreo encontrará errores de seguridad de la memoria de montón que no se encuentran mediante pruebas periódicas. Por ejemplo, GWP-ASan encontró una cantidad significativa de errores en el navegador Chrome (muchos de ellos aún están en la vista restringida).

GWP-ASan recopila información adicional sobre todas las asignaciones que intercepta. Esta información está disponible cuando GWP-ASan detecta una infracción de la seguridad de la memoria, y se posiciona automáticamente en el informe de fallas por error en código nativo, lo que puede ayudar mucho en la depuración (ver ejemplo).

GWP-ASan está diseñada para no sobrecargar a la CPU de forma significativa. GWP-ASan introduce una pequeña sobrecarga de RAM fija cuando está habilitada. El sistema Android decide esta sobrecarga, que actualmente es de aproximadamente 70 kibibytes (KiB) para cada proceso afectado.

Cómo habilitarla en la app

Las apps pueden habilitar GWP-ASan a nivel de proceso mediante la etiqueta android:gwpAsanMode en el manifiesto de la app. Se admiten las siguientes opciones:

  • Siempre inhabilitado (android:gwpAsanMode="never"): esta configuración inhabilita GWP-ASan por completo en tu app y es la opción predeterminada para las apps que no son del sistema.

  • Siempre habilitado (android:gwpAsanMode="always"): Esta configuración habilita GWP-ASan en tu app, que incluye lo siguiente:

    1. El sistema operativo reserva una cantidad fija de RAM para las operaciones de GWP-ASan, aproximadamente ~70 KiB para cada proceso afectado. (Habilita GWP-ASan si tu app no es extremadamente sensible a los aumentos en el uso de la memoria).

    2. GWP-ASan intercepta un subconjunto de asignaciones de montón aleatoriamente y las coloca en una región especial que detecta de manera confiable las infracciones a la seguridad de la memoria.

    3. Cuando se produce una infracción de la seguridad de la memoria en la región especial, GWP-ASan finaliza el proceso.

    4. GWP-ASan proporciona información adicional sobre el error en el informe de fallas.

Para habilitar GWP-ASan globalmente para tu app, agrega lo siguiente a tu archivo AndroidManifest.xml:

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

Además, GWP-ASan se puede habilitar o inhabilitar explícitamente para subprocesos específicos de tu app. Puedes orientarla hacia actividades y servicios mediante procesos explícitamente habilitados o inhabilitados de GWP-ASan. Consulta lo siguiente para ver un ejemplo:

<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>

Asistencia para desarrolladores

Estas secciones describen los errores que pueden ocurrir cuando se usa GWP-ASan, y cómo abordarlos.

Faltan seguimientos de asignación o desasignación

Si estás realizando el diagnóstico de una falla por error en código nativo para la cual parece que faltan marcos de asignación/desasignación, es probable que tu aplicación no tenga punteros de marco. GWP-ASan usa punteros de marco para registrar los seguimientos de asignación y desasignación por motivos de rendimiento, y no puede deshacer el seguimiento de pila si no están presentes. Deberás volver a compilar el código con los punteros de marco habilitados (-fno-omit-frame-pointer) para obtener seguimientos de pila de asignación y desasignación. Los punteros de marco están activados de forma predeterminada para los dispositivos arm64 y desactivados de forma predeterminada para los dispositivos arm32.

Cómo reproducir infracciones de seguridad

GWP-ASan está diseñada para detectar infracciones a la seguridad de la memoria de montón en los dispositivos de los usuarios. GWP-ASan proporciona la mayor cantidad de contexto posible sobre la falla (seguimiento de acceso de la infracción, string de causa y seguimientos de asignación/desasignación), pero aún podría ser difícil deducir cómo se produjo la infracción. Lamentablemente, como la detección de errores es probabilística, los informes de GWP-ASan suelen ser difíciles de reproducir en un dispositivo local.

En estas instancias, si el error afecta a dispositivos de 64 bits, debes usar HWAddressSanitizer (HWASan). HWASan detecta las infracciones de seguridad de la memoria de manera confiable en pilas, el montón y las secciones globales. Si ejecutas tu aplicación con HWASan, podrías reproducir de manera confiable el mismo resultado que informa GWP-ASan.

En los casos donde ejecutar la aplicación en HWASan no sea suficiente para encontrar la causa raíz de un error, debes intentar realizar una prueba de Fuzz sobre el código en cuestión. Puedes orientar tus esfuerzos de Fuzzing según la información del informe de GWP-ASan, que puede detectar y revelar problemas de estado subyacentes del código de manera confiable.

Ejemplo

Este código nativo de ejemplo tiene un error de uso después de la liberación del montón:

#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 una ejecución de prueba con el código de ejemplo anterior, GWP-ASan detectó correctamente el uso ilegal y activó el informe de fallas que figura debajo. GWP-ASan mejoró automáticamente el informe ya que proporcionó información sobre el tipo de falla, los metadatos de asignación y los seguimientos de pila de asignación y desasignación asociados.

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

Más información

Para obtener más información sobre los detalles de implementación de GWP-ASan, consulta la documentación de LLVM. Para obtener más información sobre los informes de fallas por errores en código nativo de Android, consulta Cómo diagnosticar fallas por errores en código nativo.