تذكرة GWP-ASan

‫GWP-ASan هي ميزة لتخصيص الذاكرة الأصلية تساعد في العثور على أخطاء استخدام ذاكرة معيّنة بعد تفريغها وتجاوز المخزن المؤقت للذاكرة الديناميكية. اسمه غير الرسمي هو اختصار تكراري، "GWP-ASan Will Provide Allocation SANity". على عكس HWASan أو تصحيح أخطاء تخصيص وتسرّب الذاكرة، لا تتطلّب أداة GWP-ASan مصدرًا أو إعادة تجميع (أي أنّها تعمل مع البرامج المُعدّة مسبقًا)، وتعمل على كلّ من العمليات التي تستخدم 32 بت و64 بت (على الرغم من أنّ الأعطال التي تحدث في العمليات التي تستخدم 32 بت تحتوي على معلومات أقل لتصحيح الأخطاء). يوضّح هذا الموضوع الإجراءات التي يجب اتّخاذها لتفعيل هذه الميزة في تطبيقك. يتوفّر GWP-ASan في التطبيقات التي تستهدف الإصدار 11 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 30) أو الإصدارات الأحدث.

نظرة عامة

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

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

    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 القابلة للإصلاح

يتيح الإصدار 14 من نظام التشغيل Android (مستوى واجهة برمجة التطبيقات 34) والإصدارات الأحدث استخدام أداة GWP-ASan القابلة للاسترداد، والتي تساعد المطوّرين في العثور على أخطاء تجاوز المخزن المؤقت للذاكرة الديناميكية واستخدام الذاكرة الديناميكية بعد تفريغها في مرحلة الإنتاج بدون التأثير سلبًا في تجربة المستخدم. عندما لا يتم تحديد android:gwpAsanMode في AndroidManifest.xml، يستخدم التطبيق أداة GWP-ASan القابلة للاسترداد.

تختلف ميزة GWP-ASan القابلة للاسترداد عن ميزة GWP-ASan الأساسية في ما يلي:

  1. يتم تفعيل ميزة GWP-ASan القابلة للاسترداد في حوالي% 1 فقط من عمليات تشغيل التطبيقات، وليس في كل عملية تشغيل.
  2. عند رصد خطأ في استخدام الذاكرة الديناميكية بعد إخلائها أو خطأ في تجاوز سعة الذاكرة المؤقتة، يظهر هذا الخطأ في تقرير الأعطال (ملف tombstone). يتوفّر تقرير الأعطال هذا من خلال واجهة برمجة التطبيقات ActivityManager#getHistoricalProcessExitReasons، تمامًا مثل تقرير GWP-ASan الأصلي.
  3. بدلاً من الخروج بعد تفريغ تقرير الأعطال، تسمح أداة GWP-ASan القابلة للاسترداد بحدوث تلف في الذاكرة، ويستمر التطبيق في العمل. وعلى الرغم من أنّ العملية قد تستمر كالمعتاد، لن يتم تحديد سلوك التطبيق بعد الآن. بسبب تلف الذاكرة، قد يتعطّل التطبيق في وقت ما في المستقبل، أو قد يستمر في العمل بدون أي تأثير يلاحظه المستخدم.
  4. يتم إيقاف ميزة GWP-ASan القابلة للاسترداد بعد تفريغ تقرير الأعطال. لذلك، يمكن للتطبيق الحصول على تقرير واحد فقط من GWP-ASan القابل للاسترداد لكل عملية تشغيل للتطبيق.
  5. إذا تم تثبيت معالج إشارات مخصّص في التطبيق، لن يتم استدعاؤه مطلقًا لإشارة SIGSEGV التي تشير إلى خطأ 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 انتهاكات أمان الذاكرة بشكل موثوق في Stack وHeap وGlobals. قد يؤدي تشغيل تطبيقك باستخدام 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، يُرجى الاطّلاع على مقالة تشخيص الأعطال الداخلية في نظام التشغيل.