GWP-ASan

GWP-ASan یک ویژگی تخصیص‌دهنده حافظه بومی است که به یافتن اشکالات use-after-free و heap-buffer-overflow کمک می‌کند. نام غیررسمی آن یک مخفف بازگشتی است، " G WP-ASan Will P rovide A llocation SAN ity". برخلاف HWASan یا Malloc Debug ، GWP-ASan نیازی به سورس یا کامپایل مجدد ندارد (یعنی با نسخه‌های از پیش ساخته شده کار می‌کند) و روی هر دو فرآیند ۳۲ و ۶۴ بیتی کار می‌کند (اگرچه خرابی‌های ۳۲ بیتی اطلاعات اشکال‌زدایی کمتری دارند). این مبحث اقداماتی را که باید برای فعال کردن این ویژگی در برنامه خود انجام دهید، شرح می‌دهد. GWP-ASan در برنامه‌هایی که اندروید ۱۱ (سطح API 30) یا بالاتر را هدف قرار می‌دهند، در دسترس است.

نمای کلی

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

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

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

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

برنامه خود را انتخاب کنید

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

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

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

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

    1. سیستم عامل مقدار ثابتی از رم را برای عملیات 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 قابل بازیابی

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

GWP-ASan قابل بازیابی با GWP-ASan پایه از جهات زیر متفاوت است:

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

از آنجا که خرابی‌های Recoverable GWP-ASan نشان‌دهنده‌ی موارد واقعی خرابی حافظه در دستگاه‌های کاربر نهایی است، اکیداً توصیه می‌کنیم اشکالات شناسایی‌شده توسط Recoverable 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 هدف قرار دهید، که می‌تواند به طور قابل اعتمادی مشکلات اساسی سلامت کد را تشخیص داده و آشکار کند.

مثال

این کد بومی نمونه دارای یک اشکال استفاده پس از آزادسازی در heap است:

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