GWP-ASan هي ميزة لتخصيص الذاكرة الأصلية تساعد في العثور على الأخطاء المرتبطة باستخدام ذاكرة معيّنة بعد تفريغها وبتجاوز المخزن المؤقت للذاكرة الديناميكية. واسمه غير الرسمي هو اختصار متكرّر، وهو GWP-ASan Will Provide Allocation SANity. على عكس HWASan أو Malloc Debug، لا يتطلّب GWP-ASan توفير المصدر أو إعادة التجميع (أي أنّه يعمل مع الإصدارات المسبقة)، ويعمل على كل من العمليات 32 بت و64 بت (مع أنّ الأعطال التي تحدث في العمليات 32 بت تتضمّن معلومات تصحيح أخطاء أقل). يوضّح هذا الموضوع الإجراءات التي عليك اتّخاذها لتفعيل هذه الميزة في تطبيقك. تتوفّر ميزة GWP-ASan في التطبيقات التي تستهدف الإصدار 11 من نظام التشغيل Android (المستوى 30 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.
نظرة عامة
يتم تفعيل GWP-ASan في بعض تطبيقات النظام وملفات التنفيذ الخاصة بالمنصة التي تم اختيارها عشوائيًا عند بدء تشغيل العملية (أو عند إنشاء نسخة من zygote). يمكنك تفعيل GWP-ASan في تطبيقك الخاص لمساعدتك في العثور على الأخطاء المتعلّقة بالذاكرة، ولإعداد تطبيقك ليكون متوافقًا مع ميزة ARM Memory Tagging Extension (MTE). توفّر آليات أخذ العيّنات من التخصيص أيضًا موثوقية في ما يتعلّق بطلبات البحث عن حالات الوفاة.
بعد تفعيلها، تعترض أداة GWP-ASan مجموعة فرعية من عمليات تخصيص الذاكرة العشوائية التي تم اختيارها بشكل عشوائي، وتضعها في منطقة خاصة ترصد أخطاء تلف الذاكرة العشوائية التي يصعب رصدها. مع توفّر عدد كافٍ من المستخدمين، سيؤدي معدّل أخذ العينات المنخفض هذا إلى العثور على أخطاء في أمان ذاكرة الكومة لا يتم العثور عليها من خلال الاختبارات العادية. على سبيل المثال، رصدت أداة GWP-ASan عددًا كبيرًا من الأخطاء في متصفّح Chrome (لا يزال العديد منها قيد العرض المحدود).
تجمع أداة GWP-ASan معلومات إضافية عن جميع عمليات التخصيص التي تعترضها. تتوفّر هذه المعلومات عندما ترصد أداة GWP-ASan انتهاكًا لأمان الذاكرة، ويتم وضعها تلقائيًا في تقرير الأعطال الأصلية، ما يساعد بشكل كبير في تصحيح الأخطاء (راجِع المثال).
تم تصميم GWP-ASan بدون أي تكلفة إضافية كبيرة لوحدة المعالجة المركزية. تفرض GWP-ASan مقدارًا صغيرًا وثابتًا من الحمل الزائد على ذاكرة الوصول العشوائي عند تفعيلها. يحدّد نظام التشغيل Android هذا الحمل الزائد، ويبلغ حاليًا 70 كيبيبايت (KiB) تقريبًا لكل عملية متأثرة.
الموافقة على استخدام الميزة في تطبيقك
يمكن للتطبيقات تفعيل 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 في تطبيقك، ويتضمّن ما يلي:يحتفظ نظام التشغيل بمقدار ثابت من ذاكرة الوصول العشوائي لعمليات 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
، يستخدم التطبيق أداة Recoverable GWP-ASan.
تختلف أداة Recoverable GWP-ASan عن أداة GWP-ASan الأساسية في ما يلي:
- لا يتم تفعيل GWP-ASan القابل للاسترداد إلا في% 1 تقريبًا من عمليات تشغيل التطبيقات، بدلاً من تفعيله في كل عملية تشغيل للتطبيق.
- عند رصد خطأ heap-use-after-free أو heap-buffer-overflow، يظهر هذا الخطأ في تقرير الأعطال (tombstone). يتوفّر تقرير الأعطال هذا من خلال واجهة برمجة التطبيقات
ActivityManager#getHistoricalProcessExitReasons
، تمامًا مثل أداة GWP-ASan الأصلية. - بدلاً من الخروج بعد تفريغ تقرير الأعطال، تسمح أداة Recoverable 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، يُرجى الاطّلاع على تشخيص الأعطال الداخلية في نظام التشغيل.