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 в вашем приложении, что включает в себя следующее:Операционная система резервирует фиксированный объем оперативной памяти для операций GWP-ASan, примерно ~70 КБ для каждого затронутого процесса. (Включите GWP-ASan, если ваше приложение не критически чувствительно к увеличению использования памяти.)
GWP-ASan перехватывает случайно выбранное подмножество выделений кучи и помещает их в специальную область, которая надежно обнаруживает нарушения безопасности памяти.
Когда в специальной области происходит нарушение безопасности памяти, GWP-ASan завершает процесс.
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 следующим образом:
- Восстанавливаемый GWP-ASan включается только примерно при 1% запусков приложений, а не при каждом запуске приложения.
- При обнаружении ошибки использования кучи после освобождения или переполнения буфера кучи эта ошибка появляется в отчете о сбое (надгробие). Этот отчет о сбое доступен через API
ActivityManager#getHistoricalProcessExitReasons
, такой же, как и исходный GWP-ASan. - Вместо выхода после сброса отчета о сбое Recoverable GWP-ASan допускает повреждение памяти, и приложение продолжает работать. Хотя процесс может продолжаться как обычно, поведение приложения больше не указывается. Из-за повреждения памяти приложение может аварийно завершить работу в какой-то произвольный момент в будущем или продолжить работу без каких-либо видимых для пользователя последствий.
- Восстанавливаемый GWP-ASan отключается после сброса отчета о сбое. Таким образом, приложение может получить только один восстанавливаемый отчет GWP-ASan за каждый запуск приложения.
- Если в приложении установлен пользовательский обработчик сигналов, он никогда не вызывает сигнал 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 см. в разделе Диагностика собственных сбоев .