GWP-আসান

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

সংক্ষিপ্ত বিবরণ

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

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

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

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

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

অ্যাপ ম্যানিফেস্টে android:gwpAsanMode ট্যাগ ব্যবহার করে অ্যাপগুলো প্রতি-প্রসেস স্তরে GWP-ASan সক্রিয় করতে পারে। নিম্নলিখিত বিকল্পগুলো সমর্থিত:

  • সর্বদা নিষ্ক্রিয় ( android:gwpAsanMode="never" ): এই সেটিংটি আপনার অ্যাপে GWP-ASan সম্পূর্ণরূপে নিষ্ক্রিয় করে দেয় এবং এটি নন-সিস্টেম অ্যাপগুলির জন্য ডিফল্ট সেটিং।

  • ডিফল্ট ( android:gwpAsanMode="default" অথবা অনির্দিষ্ট): অ্যান্ড্রয়েড ১৩ (এপিআই লেভেল ৩৩) এবং তার নিচের সংস্করণ - GWP-ASan নিষ্ক্রিয় থাকে। অ্যান্ড্রয়েড ১৪ (এপিআই লেভেল ৩৪) এবং তার উপরের সংস্করণ - রিকভারেবল GWP-ASan সক্রিয় থাকে।

  • সর্বদা সক্রিয় ( android:gwpAsanMode="always" ): এই সেটিংটি আপনার অ্যাপে GWP-ASan সক্রিয় করে, যার মধ্যে নিম্নলিখিত বিষয়গুলো অন্তর্ভুক্ত রয়েছে:

    1. অপারেটিং সিস্টেম GWP-ASan অপারেশনের জন্য একটি নির্দিষ্ট পরিমাণ র‍্যাম সংরক্ষণ করে, যা প্রতিটি প্রভাবিত প্রসেসের জন্য প্রায় ~70KiB। (যদি আপনার অ্যাপটি মেমরি ব্যবহারের বৃদ্ধিতে গুরুতরভাবে সংবেদনশীল না হয়, তবে 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

অ্যান্ড্রয়েড ১৪ (এপিআই লেভেল ৩৪) এবং এর পরবর্তী সংস্করণগুলো রিকভারেবল জিডব্লিউপি-এসান (Recoverable GWP-ASan) সমর্থন করে, যা ডেভেলপারদের ব্যবহারকারীর অভিজ্ঞতা খারাপ না করেই প্রোডাকশনে হিপ-বাফার-ওভারফ্লো (heap-buffer-overflow) এবং হিপ-ইউজ-আফটার-ফ্রি (heap-use-after-free) বাগ খুঁজে পেতে সাহায্য করে। যখন একটি AndroidManifest.xml ফাইলে android:gwpAsanMode নির্দিষ্ট করা থাকে না, তখন অ্যাপটি রিকভারেবল জিডব্লিউপি-এসান ব্যবহার করে।

পুনরুদ্ধারযোগ্য GWP-ASan, মূল GWP-ASan থেকে নিম্নলিখিত উপায়ে ভিন্ন:

  1. প্রতিটি অ্যাপ্লিকেশন লঞ্চের পরিবর্তে, রিকভারেবল GWP-ASan শুধুমাত্র প্রায় ১% অ্যাপ লঞ্চের ক্ষেত্রে সক্রিয় হয়।
  2. যখন কোনো হিপ-ইউজ-আফটার-ফ্রি বা হিপ-বাফার-ওভারফ্লো বাগ শনাক্ত করা হয়, তখন এই বাগটি ক্র্যাশ রিপোর্টে (টুম্বস্টোন) প্রদর্শিত হয়। এই ক্র্যাশ রিপোর্টটি মূল GWP-ASan-এর মতোই ActivityManager#getHistoricalProcessExitReasons API-এর মাধ্যমে পাওয়া যায়।
  3. ক্র্যাশ রিপোর্ট ডাম্প করার পর বন্ধ হয়ে যাওয়ার পরিবর্তে, রিকভারেবল জিডব্লিউপি-এসান মেমরি করাপশন হতে দেয় এবং অ্যাপটি চলতে থাকে। যদিও প্রসেসটি স্বাভাবিকভাবে চলতে পারে, অ্যাপটির আচরণ আর নির্দিষ্ট থাকে না। মেমরি করাপশনের কারণে, অ্যাপটি ভবিষ্যতে যেকোনো সময় ক্র্যাশ করতে পারে, অথবা ব্যবহারকারীর কাছে দৃশ্যমান কোনো প্রভাব ছাড়াই চলতে পারে।
  4. ক্র্যাশ রিপোর্ট ডাম্প করার পর রিকভারেবল GWP-ASan নিষ্ক্রিয় হয়ে যায়। তাই, একটি অ্যাপ প্রতিবার চালু হওয়ার সময় কেবল একটি রিকভারেবল GWP-ASan রিপোর্ট পেতে পারে।
  5. অ্যাপে যদি একটি কাস্টম সিগন্যাল হ্যান্ডলার ইনস্টল করা থাকে, তাহলে রিকভারেবল GWP-ASan ফল্ট নির্দেশক SIGSEGV সিগন্যালের জন্য এটিকে কখনও কল করা হয় না।

যেহেতু 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 রিপোর্টের তথ্যের উপর ভিত্তি করে আপনার ফাজিং কার্যক্রমকে লক্ষ্য নির্ধারণ করতে পারেন, যা নির্ভরযোগ্যভাবে কোডের অন্তর্নিহিত স্বাস্থ্যগত সমস্যাগুলো শনাক্ত ও প্রকাশ করতে পারে।

উদাহরণ

এই উদাহরণ নেটিভ কোডটিতে একটি হিপ ইউজ-আফটার-ফ্রি বাগ রয়েছে:

#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 ডকুমেন্টেশন দেখুন। অ্যান্ড্রয়েড নেটিভ ক্র্যাশ রিপোর্ট সম্পর্কে আরও জানতে, নেটিভ ক্র্যাশ নির্ণয় (Diagnosing Native Crashes) দেখুন।