GWP-ASan

GWP-ASan হল একটি নেটিভ মেমরি অ্যালোকেটর ফিচার যা use-after-free এবং হিপ-বাফার-ওভারফ্লো বাগ খুঁজে পেতে সাহায্য করে। এর ইনফর্মাল নাম হল একটি রিকার্সিভ অ্যাক্রোনিম,"GWP-ASan Will Provide Allocation SANity". HWASan বা Malloc Debug-এর মতো, GWP-ASan-এর জন্য সোর্স বা রিকম্পাইলেশনের (অর্থাৎ, প্রি-বিল্টের সাথে কাজ করে) প্রয়োজন হয় না এবং ৩২ ও ৬৪-বিট প্রসেস, দুটিতেই কাজ করে (যদিও ৩২-বিট ক্র্যাশে কম ডিবাগিং ইনফরমেশন থাকে)। আপনার অ্যাপে এই ফিচার চালু করার জন্য আপনাকে কী কী করতে হবে তা এই বিষয়ে উল্লেখ করা হয়েছে। GWP-ASan সেইসব অ্যাপে উপলভ্য যেগুলি Android ১১ (API লেভেল ৩০) বা তার পরবর্তী ভার্সনকে টার্গেট করে।

এক নজরে

প্রসেস স্টার্ট-আপের (বা যখন জাইগোট ফর্ক করে) সময় কিছু র‍্যান্ডমলি-বেছে নেওয়া সিস্টেম অ্যাপ্লিকেশন ও প্ল্যাটফর্ম এক্সিকিউটেবলে GWP-ASan চালু করা হয়। মেমরি-সম্পর্কিত বাগ খুঁজে পেতে এবং আপনার অ্যাপকে ARM মেমরি ট্যাগিং এক্সটেনশন (MTE) সাপোর্ট-এর জন্য প্রস্তুত করতে আপনার নিজের অ্যাপে GWP-ASan চালু করুন। অ্যালোকেশন স্যাম্পলিং মেকানিজম কোয়েরি অফ ডেথ-এর বিরুদ্ধে নির্ভরযোগ্যতা প্রদান করে।

চালু করা হলে, GWP-ASan হিপ অ্যালোকেশনের র‍্যান্ডমভাবে বেছে নেওয়া সাবসেট ইন্টারসেপ্ট করে, এবং সেগুলিকে একটি বিশেষ অঞ্চলে রাখে যা শনাক্ত করা কঠিন এমন হিপ মেমরি করাপশন বাগ ধরে। পর্যাপ্ত ব্যবহারকারী থাকলে, এই কম স্যাম্পলিং রেটও হিপ মেমরি নিরাপত্তা সংক্রান্ত বাগ খুঁজে পাবে যা নিয়মিত পরীক্ষার মাধ্যমে খুঁজে পাওয়া যায় না। যেমন, GWP-ASan Chrome ব্রাউজারে উল্লেখযোগ্য সংখ্যক বাগ খুঁজে পেয়েছে (যার মধ্যে অনেকগুলি এখনও সীমিত ভিউতে রয়েছে)।

GWP-ASan ইন্টারসেপ্ট করে এমন সব অ্যালোকেশন সম্পর্কে অতিরিক্ত তথ্য সংগ্রহ করে। GWP-ASan মেমরি নিরাপত্তা লঙ্ঘন শনাক্ত করলে এই তথ্য উপলভ্য হয় এবং অটোমেটিক নেটিভ ক্র্যাশ রিপোর্টে যোগ করা হয়, যা ডিবাগিংয়ের ক্ষেত্রে উল্লেখযোগ্যভাবে সাহায্য করতে পারে (উদাহরণ দেখুন)।

GWP-ASan এমনভাবে ডিজাইন করা হয়েছে যাতে CPU-এর উপর কোনও উল্লেখযোগ্য ওভারহেড না পড়ে। GWP-ASan চালু করা হলে, এটি একটি ছোট, নির্দিষ্ট RAM ওভারহেড চালু করে। এই ওভারহেড Android সিস্টেম দ্বারা নির্ধারিত হয় এবং বর্তমানে প্রতিটি প্রভাবিত প্রসেসের জন্য প্রায় ৭০ কিবিবাইট (KiB)।

আপনার অ্যাপে অপ্ট-ইন করুন

অ্যাপ ম্যানিফেস্টে android:gwpAsanMode ট্যাগ ব্যবহার করে, প্রসেস-প্রতি লেভেলে অ্যাপের মাধ্যমে GWP-ASan চালু করা হতে পারে। নিম্নলিখিত বিকল্পগুলি কাজ করে:

  • সবসময় বন্ধ (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. অপারেটিং সিস্টেম GWP-ASan অপারেশনের জন্য নির্দিষ্ট পরিমাণ RAM রিজার্ভ করে রাখে, প্রতিটি প্রভাবিত প্রসেসের জন্য আনুমানিক ~৭০KiB। (আপনার অ্যাপ মেমরির ব্যবহার বাড়ানোর ক্ষেত্রে অত্যন্ত সংবেদনশীল না হলে 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-কে সাপোর্ট করে, যা ডেভেলপারদের প্রোডাকশনে হিপ-বাফার-ওভারফ্লো এবং হিপ-ইউজ-আফটার-ফ্রি বাগ খুঁজে পেতে সাহায্য করে ব্যবহারকারীর অভিজ্ঞতাকে প্রভাবিত না করেই। AndroidManifest.xml-এ android:gwpAsanMode উল্লেখ করা না থাকলে, অ্যাপটি রিকভার করা যায় এমন GWP-ASan ব্যবহার করে।

নিম্নলিখিত উপায়ে রিকভার করা যায় এমন GWP-ASan বেস GWP-ASan-এর থেকে আলাদা:

  1. প্রতিটি অ্যাপ্লিকেশন লঞ্চের পরিবর্তে, শুধুমাত্র আনুমানিক ১% অ্যাপ লঞ্চের ক্ষেত্রে রিকভার করা যায় এমন GWP-ASan চালু করা হয়।
  2. হিপ-ইউজ-আফটার-ফ্রি বা হিপ-বাফার-ওভারফ্লো বাগ শনাক্ত করা হলে, এই বাগ ক্র্যাশ রিপোর্টে (টুম্বস্টোন) দেখা যায়। এই ক্র্যাশ রিপোর্ট ActivityManager#getHistoricalProcessExitReasons API-এর মাধ্যমে উপলভ্য, যা আসল GWP-ASan-এর মতোই।
  3. ক্র্যাশ রিপোর্ট ডাম্প করার পরে বেরিয়ে আসার পরিবর্তে, রিকভার করা যায় এমন 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 নেটিভ ক্র্যাশ রিপোর্ট সম্পর্কে আরও জানতে, নেটিভ ক্র্যাশ ডায়াগনসিস করা দেখুন।