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