GWP-ASan — это встроенная функция выделения памяти, которая помогает находить ошибки использования памяти после освобождения и переполнения буфера кучи . Её неофициальное название — рекурсивная аббревиатура « G WP-ASan W ill Provide 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) и выше поддерживают восстанавливаемый GWP-ASan, который помогает разработчикам обнаруживать ошибки переполнения буфера кучи и использования кучи после освобождения в рабочей среде, не ухудшая пользовательский опыт. Если android:gwpAsanMode
не указан в файле AndroidManifest.xml
, приложение использует восстанавливаемый GWP-ASan.
Восстанавливаемый GWP-ASan отличается от базового GWP-ASan следующими особенностями:
- Восстанавливаемый GWP-ASan включается только примерно при 1% запусков приложений, а не при каждом запуске приложения.
- При обнаружении ошибки использования кучи после освобождения или переполнения буфера кучи эта ошибка отображается в отчёте о сбое (захоронении). Этот отчёт доступен через API
ActivityManager#getHistoricalProcessExitReasons
, как и в оригинальном GWP-ASan. - Вместо завершения работы после создания отчёта о сбое, восстанавливаемый GWP-ASan допускает повреждение памяти, и приложение продолжает работу. Хотя процесс может продолжаться в обычном режиме, поведение приложения больше не определяется. Из-за повреждения памяти приложение может аварийно завершить работу в любой момент в будущем или продолжить её без видимых для пользователя последствий.
- Восстанавливаемый GWP-ASan отключается после создания отчёта о сбое. Таким образом, приложение может получить только один восстанавливаемый отчёт GWP-ASan за один запуск.
- Если в приложении установлен пользовательский обработчик сигналов, он никогда не будет вызываться для сигнала SIGSEGV, указывающего на восстанавливаемую ошибку GWP-ASan.
Поскольку восстанавливаемые сбои GWP-ASan указывают на реальные случаи повреждения памяти на устройствах конечных пользователей, мы настоятельно рекомендуем сортировать и исправлять ошибки, выявленные восстанавливаемым 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 недостаточен для выявления первопричины ошибки, следует попробовать фаззинг ( fuzzing) для соответствующего кода. Вы можете использовать информацию из отчёта 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 см. в разделе «Диагностика нативных сбоев» .