GWP-ASan

‫GWP-ASan ویژگی تخصیص‌دهنده حافظه محلی است که به پیدا کردن اشکال‌های use-after-free و heap-buffer-overflow کمک می‌کند. نام غیررسمی آن یک سرنام بازگشتی است: «GWP-ASan Will Provide Allocation SANity». برخلاف HWASan یا اشکال‌زدایی تخصیص حافظه، GWP-ASan به منبع یا بازهمگردانی نیاز ندارد (یعنی با پیش‌ساخته‌ها کار می‌کند) و در هر دو فرایند ۳۲ و ۶۴ بیتی کار می‌کند (اگرچه خرابی‌های ۳۲ بیتی اطلاعات اشکال‌زدایی کمتری دارند). این موضوع اقداماتی را که باید برای فعال کردن این ویژگی در برنامه‌تان انجام دهید شرح می‌دهد. GWP-ASan در برنامه‌هایی که Android 11 (سطح میانای برنامه‌سازی کاربردی ۳۰) یا بالاتر را هدف قرار می‌دهند دردسترس است.

نمای کلی

GWP-ASan در برخی‌از برنامه‌های سیستم و فایل‌های اجرایی پلاتفرم که به‌طور تصادفی انتخاب شده‌اند هنگام راه‌اندازی فرایند (یا زمانی که زیگوت منشعب می‌شود) فعال می‌شود. GWP-ASan را در برنامه خودتان فعال کنید تا به شما کمک کند اشکال‌های مربوط به حافظه را پیدا کنید و برنامه‌تان را برای پشتیبانی از ARM Memory Tagging Extension (MTE) آماده کنید. سازوکارهای نمونه‌گیری تخصیص همچنین اطمینان‌پذیری دربرابر پرس‌وجوهای مرگ را فراهم می‌کنند.

پس‌از فعال شدن، GWP-ASan زیرمجموعه‌ای از تخصیص‌های پشته را که به‌طور تصادفی انتخاب شده‌اند رهگیری می‌کند، و آن‌ها را در منطقه‌ای خاص قرار می‌دهد که اشکال‌های خرابی حافظه پشته را که تشخیص آن‌ها دشوار است شناسایی می‌کند. اگر تعداد کاربران کافی باشد، حتی این نرخ نمونه‌برداری پایین نیز اشکال‌های ایمنی حافظه پشته را که ازطریق آزمایش معمولی پیدا نمی‌شوند پیدا خواهد کرد. برای مثال، GWP-ASan تعداد قابل‌توجهی اشکال در مرورگر Chrome پیدا کرده است (بسیاری از آن‌ها هنوز در نمای محدودشده هستند).

‫GWP-ASan اطلاعات اضافی درباره همه تخصیص‌هایی که رهگیری می‌کند جمع‌آوری می‌کند. این اطلاعات زمانی دردسترس است که GWP-ASan نقض ایمنی حافظه را شناسایی کند و به‌طور خودکار در گزارش خرابی بومی قرار گیرد، که می‌تواند کمک قابل‌توجهی در اشکال‌زدایی باشد (مثال را ببینید).

GWP-ASan به‌گونه‌ای طراحی شده است که هیچ سربار CPU قابل‌توجهی ایجاد نکند. GWP-ASan درصورت فعال بودن، سربار RAM کوچک و ثابتی را معرفی می‌کند. این سربار توسط سیستم Android تعیین می‌شود و درحال‌حاضر تقریباً ۷۰ کیبی‌بایت (KiB) برای هر فرایند تحت‌تأثیر قرارگرفته است.

موافقت کردن با برنامه

GWP-ASan ممکن است توسط برنامه‌ها در سطح هر فرایند بااستفاده از برچسب android:gwpAsanMode در مانیفست برنامه فعال شود. گزینه‌های زیر پشتیبانی می‌شود:

  • همیشه غیرفعال (android:gwpAsanMode="never"): این تنظیم GWP-ASan را به‌طور کامل در برنامه‌تان غیرفعال می‌کند و برای برنامه‌های غیرسیستمی پیش‌فرض است.

  • پیش‌فرض (android:gwpAsanMode="default" یا نامشخص): Android 13 (سطح میانای برنامه‌سازی کاربردی ۳۳) و پایین‌تر - GWP-ASan غیرفعال است. Android 14 (سطح میانای برنامه‌سازی کاربردی ۳۴) و بالاتر - GWP-ASan قابل‌بازیابی فعال است.

  • همیشه فعال (android:gwpAsanMode="always"): این تنظیم GWP-ASan را در برنامه‌تان فعال می‌کند که شامل موارد زیر است:

    1. سیستم‌عامل مقدار ثابتی از RAM را برای عملیات GWP-ASan رزرو می‌کند، تقریباً ۷۰ کیبی‌بایت برای هر فرایند تحت‌تأثیر قرارگرفته. (اگر برنامه شما نسبت‌به افزایش استفاده از حافظه حساسیت بحرانی ندارد، 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 قابل‌بازیابی

‫Android 14 (سطح API‏ ۳۴) و بالاتر از Recoverable GWP-ASan پشتیبانی می‌کند که به توسعه‌دهندگان کمک می‌کند اشکال‌های heap-buffer-overflow و heap-use-after-free را در تولید بدون کاهش کیفیت تجربه کاربری پیدا کنند. وقتی android:gwpAsanMode در AndroidManifest.xml مشخص نشده باشد، برنامه از GWP-ASan قابل‌بازیابی استفاده می‌کند.

GWP-ASan قابل‌بازیابی از جنبه‌های زیر با GWP-ASan پایه تفاوت دارد:

  1. GWP-ASan قابل‌بازیابی فقط در تقریباً ۱٪ از راه‌اندازی‌های برنامه فعال می‌شود، نه در هر راه‌اندازی برنامه.
  2. وقتی اشکال heap-use-after-free یا heap-buffer-overflow شناسایی می‌شود، این اشکال در گزارش خرابی (سنگ قبر) نشان داده می‌شود. این گزارش خرابی ازطریق ActivityManager#getHistoricalProcessExitReasons API دردسترس است، درست مانند GWP-ASan اصلی.
  3. ‫Recoverable GWP-ASan به‌جای اینکه پس‌از تخلیه گزارش خرابی خارج شود، اجازه می‌دهد خرابی حافظه رخ دهد و برنامه به اجرا ادامه می‌دهد. درحالی‌که ممکن است فرایند طبق معمول ادامه یابد، رفتار برنامه دیگر مشخص نیست. به‌دلیل خرابی حافظه، ممکن است برنامه در نقطه‌ای دلخواه در آینده ازکار بیفتد، یا ممکن است بدون هیچ تأثیر قابل‌مشاهده‌ای برای کاربر ادامه یابد.
  4. GWP-ASan قابل‌بازیابی پس‌از تخلیه گزارش خرابی غیرفعال می‌شود. بنابراین، برنامه فقط می‌تواند یک گزارش GWP-ASan قابل‌بازیابی به‌ازای هر راه‌اندازی برنامه دریافت کند.
  5. اگر یک کنترل‌کننده سیگنال سفارشی در برنامه نصب شده باشد، هرگز برای سیگنال SIGSEGV که نشان‌دهنده خطای GWP-ASan قابل‌بازیابی است فراخوانده نمی‌شود.

ازآنجایی‌که خرابی‌های GWP-ASan قابل‌بازیابی نشان‌دهنده موارد واقعی خرابی حافظه در دستگاه‌های کاربر نهایی است، اکیداً توصیه می‌کنیم اشکال‌های شناسایی‌شده توسط GWP-ASan قابل‌بازیابی را با اولویت بالا تریاژ و رفع کنید.

پشتیبانی برنامه‌نویس

این بخش‌ها مشکلاتی را که ممکن است هنگام استفاده از GWP-ASan رخ دهد و نحوه رسیدگی به آن‌ها را شرح می‌دهند.

ردپای تخصیص/لغو تخصیص وجود ندارد

اگر درحال عیب‌یابی خرابی بومی هستید که به‌نظر می‌رسد فریم‌های تخصیص/لغو تخصیص را ندارد، احتمالاً برنامه شما فاقد اشاره‌گرهای فریم است. GWP-ASan از اشاره‌گرهای قاب برای ثبت ردگیری‌های تخصیص و لغو تخصیص به‌دلایل عملکردی استفاده می‌کند و اگر این اشاره‌گرها وجود نداشته باشند، نمی‌تواند ردگیری پشته را باز کند.

اشاره‌گرهای قاب به‌طور پیش‌فرض برای دستگاه‌های arm64 روشن و برای دستگاه‌های arm32 خاموش هستند. چون برنامه‌ها کنترلی بر libc ندارند، (به‌طور کلی) امکان ندارد GWP-ASan بتواند ردگیری‌های تخصیص/لغو تخصیص را برای برنامه‌ها یا فایل‌های اجرایی ۳۲ بیتی جمع‌آوری کند. برنامه‌های ۶۴ بیتی باید مطمئن شوند که با -fomit-frame-pointer ساخته نشده‌اند تا GWP-ASan بتواند ردیابی‌های پشته تخصیص و لغو تخصیص را جمع‌آوری کند.

بازتولید نقض‌های ایمنی

‫GWP-ASan برای شناسایی نقض‌های ایمنی حافظه پشته در دستگاه‌های کاربر طراحی شده است. GWP-ASan تا حد امکان بافت بیشتری درباره ازکارافتادگی ارائه می‌دهد (رد دسترسی نقض، رشته علت، و ردهای تخصیص/لغو تخصیص)، اما ممکن است هنوز هم استنتاج نحوه وقوع نقض دشوار باشد. متأسفانه، ازآنجایی‌که تشخیص اشکال احتمالی است، بازتولید گزارش‌های GWP-ASan اغلب در دستگاه محلی دشوار است.

در این موارد، اگر اشکال بر دستگاه‌های ۶۴ بیتی تأثیر می‌گذارد، باید از 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، به تشخیص خرابی‌های بومی مراجعه کنید.