ГВП-АСан

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

Обзор

GWP-ASan включается в некоторых случайно выбранных системных приложениях и исполняемых файлах платформы при запуске процесса (или при разветвлении зиготы). Включите GWP-ASan в своем приложении, чтобы помочь найти ошибки, связанные с памятью, и подготовить приложение к поддержке расширения тегов памяти ARM (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 , приложение использует восстанавливаемый 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, который может надежно обнаружить и выявить основные проблемы работоспособности кода.

Пример

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

#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 см. в разделе Диагностика собственных сбоев .