GWP-ASan هي ميزة من ميزات تخصيص الذاكرة الأصلية التي تساعد في العثور على الأخطاء البرمجية use-after-free وheap-buffer-overflow. واسمها غير الرسمي هو اختصار متكرر، وهو "GWP-ASan Will Poffide Allocation SANity". على عكس HWASan أو Malloc Debug، لا تتطلب أداة GWP-ASan استخدام مصدر أو إعادة تجميع (أي تعمل مع العمليات التي يتم إنشاؤها مسبقًا)، وتعمل على العمليات التي تعمل بالإصدار 32 بت و64 بت (على الرغم من أن الأعطال التي تعمل بالإصدار 32 بت تتضمن معلومات تصحيح أخطاء أقل). يوضِّح هذا الموضوع الإجراءات التي يجب اتّخاذها لتفعيل هذه الميزة في تطبيقك. يتوفّر GWP-ASan على التطبيقات التي تستهدف الإصدار Android 11 (المستوى 30 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.
نظرة عامة
يتم تفعيل GWP-ASan في بعض تطبيقات النظام التي يتم اختيارها عشوائيًا والملفات القابلة للتنفيذ على النظام الأساسي عند بدء تشغيل العملية (أو عند إجراء شوكات zygote). عليك تفعيل GWP-ASan في تطبيقك الخاص لمساعدتك في رصد الأخطاء المتعلّقة بالذاكرة، وإعداد تطبيقك لإتاحة إضافة وضع علامات الذاكرة (MTE) ضمن ARM. توفّر آليات أخذ العينات أيضًا إمكانية الاعتماد على طلبات البحث عن الوفاة.
بعد تفعيل أداة GWP-ASan، يعترض التطبيق مجموعة فرعية يتم اختيارها عشوائيًا من عمليات توزيع كومة الذاكرة المؤقتة، ويضعها في منطقة خاصة ترصد أخطاء تلف الذاكرة التي يصعب رصدها. وبالنظر إلى عدد كافٍ من المستخدمين، فإن معدل أخذ العينات المنخفض هذا سيجد أخطاء أمان الذاكرة التي لا يتم اكتشافها من خلال الاختبار المنتظم. على سبيل المثال، رصد فريق GWP-ASan عددًا كبيرًا من الأخطاء في متصفّح Chrome (لا يزال العديد منها خاضعًا لقيود العرض).
يجمع GWP-ASan معلومات إضافية حول جميع التوزيعات التي يعترضها. تتوفر هذه المعلومات عندما يرصد أداة GWP-ASan انتهاكًا لسلامة الذاكرة، ويتم إدراجها تلقائيًا في تقرير الأعطال الأصلي، ما يمكن أن يساعد بشكل كبير في تصحيح الأخطاء (راجِع مثال).
تم تصميم GWP-ASan بحيث لا يتحمّل أي أعباء كبيرة من استخدام وحدة المعالجة المركزية (CPU). يقدم GWP-ASan مقدارًا إضافيًا من ذاكرة الوصول العشوائي وثابتًا عند تفعيله. ويحدّد نظام Android هذا الحد الأقصى ويصل حاليًا إلى ما يقرب من 70 كيبيبايت (KiB) لكل عملية متأثرة.
تفعيل تطبيقك
قد يتم تفعيل GWP-ASan من خلال التطبيقات على مستوى كل عملية باستخدام
علامة android:gwpAsanMode
في بيان التطبيق. الخيارات التالية متاحة:
غير مفعّلة دائمًا (
android:gwpAsanMode="never"
): يؤدي هذا الإعداد إلى إيقاف 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 وكيفية معالجتها.
آثار التخصيص/التوزيع غير متوفرة.
إذا كنت بصدد تشخيص عطل أصلي يبدو أنه ينقصه إطارات تخصيص/إلغاء التخصيص، فمن المحتمل أن تطبيقك يفتقد إلى مؤشرات الإطارات. يستخدم 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، يمكنك الاطّلاع على تشخيص الأعطال الأصلية.