تذكرة GWP-ASan

GWP-ASan هي ميزة من ميزات تخصيص الذاكرة الأصلية تساعدك في العثور على الأخطاء الأخطاء use-after-free وheap-buffer-overflow. واسمها غير الرسمي هو اختصار متكرّر، وهو "GWP-ASan Will Poffide A llocation SANity". على عكس HWASan أو Malloc Debug، لا تتطلب أداة GWP-ASan استخدام المصدر أو إعادة التحويل البرمجي (أي أنّها تعمل مع الإصدارات مسبقة الإنشاء)، وتعمل مع عمليات الإصدار 32 و64 بت (على الرغم من أنّ أعطال الإصدار 32 بت تحتوي على معلومات أقل حول تصحيح الأخطاء). يوضّح هذا الموضوع الإجراءات التي يجب اتّخاذها لتفعيل هذه الميزة في تطبيقك. يتوفّر GWP-ASan على التطبيقات التي تستهدف الإصدار Android 11 (المستوى 30 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث.

نظرة عامة

يتم تفعيل GWP-ASan في بعض تطبيقات النظام التي يتم اختيارها عشوائيًا وعمليات التنفيذ في النظام الأساسي عند بدء العملية (أو عند استخدام شوكات zygote). عليك تفعيل GWP-ASan في تطبيقك الخاص لمساعدتك في العثور على الأخطاء المرتبطة بالذاكرة، ولإعداد تطبيقك للتوافق مع الإضافة "إضافة وضع علامات الذاكرة" (ARM Memory Taging (MTE). توفّر آليات أخذ العينات من التخصيص أيضًا الموثوقية في مقابل طلبات الوفاة.

بعد تفعيل 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 في تطبيقك، والذي يتضمّن ما يلي:

    1. يحتفظ نظام التشغيل بمقدار ثابت من ذاكرة الوصول العشوائي لعمليات GWP-ASan، حوالي 70 كيلوبايت لكل عملية متأثرة. (يمكنك تفعيل GWP-ASan إذا لم يكن تطبيقك حساسًا للغاية لزيادة استخدام الذاكرة).

    2. يعترض GWP-ASan مجموعة فرعية مختارة عشوائيًا من تخصيصات كومة الذاكرة المؤقتة ويضعها في منطقة خاصة يمكنها رصد انتهاكات أمان الذاكرة بشكل موثوق.

    3. عند حدوث انتهاك لأمان الذاكرة في منطقة خاصة، تنهي GWP-ASan العملية.

    4. يوفر 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 الأصلية، يمكنك الاطّلاع على تشخيص الأعطال المدمجة مع المحتوى.