GWP-ASan — это встроенная функция выделения памяти, которая помогает находить ошибки использования после освобождения и переполнения буфера кучи . Ее неофициальное название — рекурсивная аббревиатура « G WP-ASan W ill P rovide A llocation SAN ity». В отличие от 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 в вашем приложении, что включает следующее:Операционная система резервирует фиксированный объем оперативной памяти для операций 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, который помогает разработчикам находить ошибки heap-buffer-overflow и heap-use-after-free в производстве без ухудшения пользовательского опыта. Если android:gwpAsanMode
не указан в AndroidManifest.xml
, приложение использует Recoverable GWP-ASan.
Восстанавливаемый GWP-ASan отличается от базового GWP-ASan следующим:
- Восстанавливаемый GWP-ASan включается только примерно в 1% запусков приложений, а не при каждом запуске приложения.
- При обнаружении ошибки heap-use-after-free или heap-buffer-overflow эта ошибка отображается в отчете о сбое (надгробии). Этот отчет о сбое доступен через 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, см. Диагностика собственных сбоев .