ГВП-АСан

GWP-ASan — это встроенная функция распределения памяти, которая помогает обнаруживать ошибки использования памяти после освобождения (use-after-free) и переполнения буфера кучи (heap-buffer-overflow ). Её неофициальное название — рекурсивная аббревиатура: « GWP -ASan Will Provide Allocation SANity » («GWP-ASan Will Provide Allocation SANity»). В отличие от HWASan или Malloc Debug , GWP-ASan не требует перекомпиляции исходного кода (то есть работает с предварительно собранными версиями) и работает как на 32-битных, так и на 64-битных процессах (хотя при сбоях в 32-битных системах отладочной информации меньше ). В этом разделе описаны действия, необходимые для включения этой функции в вашем приложении. GWP-ASan доступен в приложениях, ориентированных на Android 11 (уровень API 30) или выше.

Обзор

GWP-ASan включается в некоторых случайно выбранных системных приложениях и исполняемых файлах платформы при запуске процесса (или при создании дочернего процесса). Включите GWP-ASan в своем приложении, чтобы помочь вам найти ошибки, связанные с памятью, и подготовить ваше приложение к поддержке расширения ARM Memory Tagging Extension (MTE) . Механизмы выборки выделения памяти также обеспечивают надежность в отношении запросов о завершении работы .

После включения GWP-ASan перехватывает случайно выбранное подмножество выделений памяти в куче и помещает их в специальную область, которая выявляет труднообнаружимые ошибки повреждения памяти в куче. При достаточном количестве пользователей даже такая низкая частота выборки позволит обнаружить ошибки безопасности памяти в куче, которые не выявляются при обычном тестировании. Например, GWP-ASan обнаружил значительное количество ошибок в браузере Chrome (многие из которых до сих пор находятся в режиме ограниченного доступа).

GWP-ASan собирает дополнительную информацию обо всех перехваченных выделениях памяти. Эта информация становится доступна, когда GWP-ASan обнаруживает нарушение безопасности памяти, и автоматически добавляется в собственный отчет о сбое, что может значительно помочь в отладке (см. пример ).

GWP-ASan разработан таким образом, чтобы не создавать значительной нагрузки на процессор. При включении GWP-ASan вносит небольшой фиксированный объем оперативной памяти. Этот объем определяется системой Android и в настоящее время составляет приблизительно 70 кибибайт (КиБ) для каждого затронутого процесса.

Подключите ваше приложение

GWP-ASan может быть включен приложениями на уровне процесса с помощью тега android:gwpAsanMode в манифесте приложения. Поддерживаются следующие параметры:

  • Всегда отключено ( android:gwpAsanMode="never" ): Этот параметр полностью отключает GWP-ASan в вашем приложении и является значением по умолчанию для приложений, не являющихся системными.

  • По умолчанию ( android:gwpAsanMode="default" или не указано): Android 13 (уровень API 33) и ниже — GWP-ASan отключен. Android 14 (уровень API 34) и выше — восстанавливаемый GWP-ASan включен.

  • Всегда включено ( android:gwpAsanMode="always" ): Этот параметр включает GWP-ASan в вашем приложении, что включает в себя следующее:

    1. Операционная система резервирует фиксированный объем оперативной памяти для операций GWP-ASan, примерно ~70 КиБ на каждый затронутый процесс. (Включите GWP-ASan, если ваше приложение не критически чувствительно к увеличению использования памяти.)

    2. GWP-ASan перехватывает случайно выбранное подмножество выделений памяти в куче и помещает их в специальную область, которая надежно обнаруживает нарушения безопасности памяти.

    3. При возникновении нарушения безопасности памяти в специальной области GWP-ASan завершает процесс.

    4. В отчете о ДТП организация GWP-ASan предоставляет дополнительную информацию о неисправности.

Чтобы включить GWP-ASan глобально для вашего приложения, добавьте следующее в файл AndroidManifest.xml :

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

Кроме того, GWP-ASan можно явно включить или отключить для конкретных подпроцессов вашего приложения. Вы можете выбирать действия и сервисы, используя процессы, которые явно включили или отключили GWP-ASan. Пример приведен ниже:

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

Восстанавливаемый GWP-ASan

Android 14 (уровень API 34) и выше поддерживают Recoverable GWP-ASan, что помогает разработчикам находить ошибки переполнения буфера кучи и использования кучи после освобождения памяти в производственной среде без ухудшения пользовательского опыта. Если android:gwpAsanMode не указан в файле AndroidManifest.xml , приложение использует Recoverable GWP-ASan.

Восстанавливаемый GWP-ASan отличается от базового GWP-ASan следующими способами:

  1. Восстанавливаемый GWP-ASan активируется лишь примерно в 1% случаев запуска приложений, а не при каждом запуске.
  2. При обнаружении ошибки, связанной с использованием памяти после освобождения или переполнением буфера кучи, эта ошибка отображается в отчете о сбое (надгробном камне). Этот отчет о сбое доступен через API ActivityManager#getHistoricalProcessExitReasons , аналогично оригинальному GWP-ASan.
  3. Вместо завершения работы после сохранения отчета о сбое, Recoverable GWP-ASan допускает повреждение памяти, и приложение продолжает работу. Хотя процесс может продолжаться как обычно, поведение приложения больше не определяется. Из-за повреждения памяти приложение может аварийно завершить работу в какой-либо произвольный момент в будущем или продолжить работу без каких-либо видимых для пользователя последствий.
  4. Функция восстановления GWP-ASan отключается после создания отчета о сбое. Поэтому приложение может получить только один отчет о восстановлении GWP-ASan за один запуск.
  5. Если в приложении установлен пользовательский обработчик сигналов, он никогда не будет вызываться для сигнала SIGSEGV, указывающего на восстанавливаемую ошибку GWP-ASan.

Поскольку сбои Recoverable GWP-ASan указывают на реальные случаи повреждения памяти на устройствах конечных пользователей, мы настоятельно рекомендуем проводить первоочередную проверку и исправление ошибок, выявленных Recoverable GWP-ASan, в приоритетном порядке.

Поддержка разработчиков

В этих разделах описаны проблемы, которые могут возникнуть при использовании GWP-ASan, и способы их решения.

Отсутствуют трассировки выделения/освобождения памяти.

Если вы диагностируете сбой в работе нативной программы, который, по всей видимости, связан с отсутствием кадров выделения/освобождения памяти, вероятно, в вашем приложении отсутствуют указатели кадров . GWP-ASan использует указатели кадров для записи трассировки выделения и освобождения памяти в целях повышения производительности и не может восстановить трассировку стека, если они отсутствуют.

Указатели кадров по умолчанию включены для устройств arm64 и выключены для устройств arm32. Поскольку приложения не контролируют libc, GWP-ASan (как правило) не может собирать трассировки выделения/освобождения памяти для 32-битных исполняемых файлов или приложений. 64-битные приложения должны убедиться, что они не собраны с -fomit-frame-pointer чтобы GWP-ASan мог собирать трассировки стека выделения и освобождения памяти.

Воспроизведение нарушений правил техники безопасности

GWP-ASan предназначен для обнаружения нарушений безопасности памяти в куче на пользовательских устройствах. GWP-ASan предоставляет максимально возможный контекст о сбое (трассировка доступа к нарушению, строка причины и трассировки выделения/освобождения памяти), но все равно может быть сложно определить, как произошло нарушение. К сожалению, поскольку обнаружение ошибок носит вероятностный характер, отчеты GWP-ASan часто сложно воспроизвести на локальном устройстве.

В таких случаях, если ошибка затрагивает 64-битные устройства, следует использовать HWAddressSanitizer (HWASan). HWASan надежно обнаруживает нарушения безопасности памяти в стеке, куче и глобальных переменных. Запуск вашего приложения с использованием HWASan может надежно воспроизвести тот же результат, о котором сообщает GWP-ASan.

В тех случаях, когда запуск приложения под HWASan не позволяет выявить причину ошибки, следует попробовать провести фаззинг кода. Целенаправленные действия по фаззингу можно осуществлять на основе информации из отчета GWP-ASan, который позволяет надежно обнаруживать и выявлять скрытые проблемы в коде.

Пример

В этом примере нативного кода обнаружена ошибка использования памяти после освобождения (use-after-free) в куче:

#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));
}

В ходе тестового запуска с использованием приведенного выше примера кода GWP-ASan успешно обнаружил недопустимое использование и сгенерировал отчет о сбое, представленный ниже. GWP-ASan автоматически дополнил отчет, предоставив информацию о типе сбоя, метаданных выделения памяти, а также соответствующих трассировках стека выделения и освобождения памяти.

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

Более подробная информация

Чтобы узнать больше о деталях реализации GWP-ASan, см. документацию LLVM . Чтобы узнать больше об отчетах о сбоях в нативных приложениях Android, см. раздел «Диагностика сбоев в нативных приложениях» .