GWP-ASan

GWP-ASan یک ویژگی تخصیص دهنده حافظه بومی است که به یافتن اشکالات استفاده پس از استفاده رایگان و سرریز بافر پشته کمک می کند. نام غیررسمی آن یک مخفف بازگشتی است " G WP-ASan W ill P rovide A location SAN ity". برخلاف HWASan یا Malloc Debug ، GWP-ASan نیازی به منبع یا کامپایل مجدد ندارد (یعنی با پیش ساخته‌ها کار می‌کند)، و روی هر دو فرآیند 32 و 64 بیتی کار می‌کند (اگرچه خرابی‌های 32 بیتی اطلاعات اشکال‌زدایی کمتری دارند). این موضوع اقداماتی را که باید برای فعال کردن این ویژگی در برنامه خود انجام دهید، تشریح می کند. GWP-ASan در برنامه‌هایی موجود است که Android 11 (سطح API 30) یا بالاتر را هدف قرار می‌دهند.

نمای کلی

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

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

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

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

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

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

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

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

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

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

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

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

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

پشتیبانی از توسعه دهندگان

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

ردیابی تخصیص/تخصیص وجود ندارد

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

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

بازتولید تخلفات ایمنی

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

در این موارد، اگر اشکال بر دستگاه‌های 64 بیتی تأثیر بگذارد، باید از HWAddressSanitizer (HWASan) استفاده کنید. HWASan نقض ایمنی حافظه را به طور قابل اعتماد در stack، heap و global شناسایی می کند. اجرای برنامه شما با HWASan ممکن است به طور قابل اعتماد همان نتیجه ای را که توسط GWP-ASan گزارش شده است بازتولید کند.

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