जीडब्ल्यूपी-असैन

GWP-ASan, नेटिव मेमोरी ऐलोकेटर की एक सुविधा है. इससे यूज़-आफ़्टर-फ़्री और हीप-बफ़र-ओवरफ़्लो बग का पता लगाने में मदद मिलती है. इसका अनौपचारिक नाम एक रिकर्सिव एक्रोनियम है,"GWP-ASan Will Provide Allocation SANity". HWASan या Malloc Debug के उलट, GWP-ASan को सोर्स या रिकांपाइलेशन की ज़रूरत नहीं होती. इसका मतलब है कि यह प्रीबिल्ट के साथ काम करता है. साथ ही, यह 32-बिट और 64-बिट, दोनों प्रोसेस पर काम करता है. हालांकि, 32-बिट क्रैश में डीबग करने से जुड़ी कम जानकारी मिलती है. इस विषय में, आपके ऐप्लिकेशन में इस सुविधा को चालू करने के लिए ज़रूरी कार्रवाइयों के बारे में बताया गया है. GWP-ASan, उन ऐप्लिकेशन पर उपलब्ध है जो Android 11 (एपीआई लेवल 30) या उसके बाद के वर्शन को टारगेट करते हैं.

खास जानकारी

GWP-ASan, प्रोसेस शुरू होने पर (या जब ज़ायगोट फ़ोर्क होता है), सिस्टम के कुछ ऐप्लिकेशन और प्लैटफ़ॉर्म एक्ज़ीक्यूटेबल पर चालू होता है. अपने ऐप्लिकेशन में GWP-ASan को चालू करें. इससे आपको मेमोरी से जुड़े बग ढूंढने में मदद मिलेगी. साथ ही, ARM Memory Tagging Extension (MTE) के साथ काम करने के लिए, अपने ऐप्लिकेशन को तैयार करने में मदद मिलेगी. सैंपलिंग के लिए इस्तेमाल किए जाने वाले तरीके, मौत से जुड़ी क्वेरी के जवाब देने में भी भरोसेमंद होते हैं.

चालू होने पर, GWP-ASan, हीप के लिए रैंडम तरीके से चुने गए सबसेट को इंटरसेप्ट करता है. इसके बाद, उन्हें एक खास क्षेत्र में रखता है. इससे, हीप मेमोरी करप्शन के ऐसे बग का पता चलता है जिनका पता लगाना मुश्किल होता है. अगर उपयोगकर्ताओं की संख्या ज़्यादा है, तो सैंपलिंग की यह कम दर भी ढेर मेमोरी की सुरक्षा से जुड़े ऐसे बग ढूंढ लेगी जो सामान्य टेस्टिंग के दौरान नहीं मिलते. उदाहरण के लिए, GWP-ASan को Chrome ब्राउज़र में कई बग मिले हैं. इनमें से कई बग अब भी प्रतिबंधित व्यू में हैं.

GWP-ASan, उन सभी मेमोरी लोकेशन के बारे में अतिरिक्त जानकारी इकट्ठा करता है जिन्हें वह इंटरसेप्ट करता है. यह जानकारी तब उपलब्ध होती है, जब GWP-ASan को मेमोरी सेफ़्टी के उल्लंघन का पता चलता है. साथ ही, इसे नेटिव क्रैश रिपोर्ट में अपने-आप शामिल कर दिया जाता है. इससे डीबग करने में काफ़ी मदद मिल सकती है (उदाहरण देखें).

GWP-ASan को इस तरह से डिज़ाइन किया गया है कि इससे सीपीयू पर कोई खास असर न पड़े. GWP-ASan को चालू करने पर, रैम पर थोड़ा सा फ़िक्स ओवरहेड पड़ता है. इस ओवरहेड का फ़ैसला Android सिस्टम करता है. फ़िलहाल, यह हर प्रोसेस के लिए करीब 70 किबीबाइट (केआईबी) है.

अपने ऐप्लिकेशन के लिए ऑप्ट-इन करना

GWP-ASan को ऐप्लिकेशन में हर प्रोसेस के हिसाब से चालू किया जा सकता है. इसके लिए, ऐप्लिकेशन मेनिफ़ेस्ट में android:gwpAsanMode टैग का इस्तेमाल करें. ये विकल्प काम करते हैं:

  • हमेशा बंद (android:gwpAsanMode="never"): यह सेटिंग, आपके ऐप्लिकेशन में GWP-ASan को पूरी तरह से बंद कर देती है. यह सिस्टम ऐप्लिकेशन के अलावा अन्य ऐप्लिकेशन के लिए डिफ़ॉल्ट सेटिंग होती है.

  • डिफ़ॉल्ट (android:gwpAsanMode="default" या तय नहीं किया गया): Android 13 (एपीआई लेवल 33) और इससे पहले के वर्शन - GWP-ASan बंद है. Android 14 (एपीआई लेवल 34) और इसके बाद के वर्शन - Recoverable GWP-ASan चालू है.

  • हमेशा चालू (android:gwpAsanMode="always"): इस सेटिंग को चालू करने पर, आपके ऐप्लिकेशन में GWP-ASan चालू हो जाता है. इसमें ये शामिल हैं:

    1. ऑपरेटिंग सिस्टम, 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>

Recoverable GWP-ASan

Android 14 (एपीआई लेवल 34) और इसके बाद के वर्शन में, Recoverable GWP-ASan की सुविधा काम करती है. इससे डेवलपर को प्रोडक्शन में, heap-buffer-overflow और heap-use-after-free बग का पता लगाने में मदद मिलती है. साथ ही, इससे उपयोगकर्ता अनुभव पर भी कोई असर नहीं पड़ता. जब किसी AndroidManifest.xml में android:gwpAsanMode के बारे में नहीं बताया जाता है, तो ऐप्लिकेशन Recoverable GWP-ASan का इस्तेमाल करता है.

रिकवर किए जा सकने वाले GWP-ASan और बेस GWP-ASan में ये अंतर होते हैं:

  1. रिकवर किए जा सकने वाले GWP-ASan को सिर्फ़ 1% ऐप्लिकेशन लॉन्च पर चालू किया जाता है. ऐसा हर ऐप्लिकेशन लॉन्च पर नहीं किया जाता.
  2. जब हीप-यूज़-आफ़्टर-फ़्री या हीप-बफ़र-ओवरफ़्लो गड़बड़ी का पता चलता है, तो यह गड़बड़ी क्रैश रिपोर्ट (टॉम्बस्टोन) में दिखती है. यह क्रैश रिपोर्ट, ActivityManager#getHistoricalProcessExitReasons API के ज़रिए उपलब्ध है. यह ओरिजनल GWP-ASan की तरह ही है.
  3. क्रैश रिपोर्ट डंप करने के बाद बंद होने के बजाय, Recoverable GWP-ASan, मेमोरी करप्ट होने की अनुमति देता है. साथ ही, ऐप्लिकेशन चलता रहता है. प्रोसेस पहले की तरह जारी रहेगी. हालांकि, ऐप्लिकेशन के व्यवहार के बारे में अब कोई जानकारी नहीं दी गई है. मेमोरी करप्ट होने की वजह से, ऐप्लिकेशन आने वाले समय में किसी भी समय क्रैश हो सकता है. ऐसा भी हो सकता है कि ऐप्लिकेशन बिना किसी रुकावट के चलता रहे.
  4. क्रैश रिपोर्ट डंप होने के बाद, रिकवर किए जा सकने वाले GWP-ASan को बंद कर दिया जाता है. इसलिए, ऐप्लिकेशन लॉन्च होने पर, किसी ऐप्लिकेशन को सिर्फ़ एक Recoverable GWP-ASan रिपोर्ट मिल सकती है.
  5. अगर ऐप्लिकेशन में कस्टम सिग्नल हैंडलर इंस्टॉल किया गया है, तो उसे कभी भी SIGSEGV सिग्नल के लिए कॉल नहीं किया जाता. यह सिग्नल, ठीक किए जा सकने वाले GWP-ASan फ़ॉल्ट का संकेत देता है.

GWP-ASan के ज़रिए रिपोर्ट की गई, मेमोरी की गड़बड़ी से जुड़ी जानकारी

डेवलपर सहायता

इन सेक्शन में, GWP-ASan का इस्तेमाल करते समय होने वाली समस्याओं के बारे में बताया गया है. साथ ही, उन्हें ठीक करने का तरीका भी बताया गया है.

बंटन/अनबंटन के निशान मौजूद नहीं हैं

अगर आपको किसी ऐसे नेटिव क्रैश का पता लगाना है जिसमें ऐलोकेशन/डीऐलोकेशन फ़्रेम मौजूद नहीं हैं, तो हो सकता है कि आपके ऐप्लिकेशन में फ़्रेम पॉइंटर मौजूद न हों. GWP-ASan, परफ़ॉर्मेंस को बेहतर बनाने के लिए फ़्रेम पॉइंटर का इस्तेमाल करता है. इससे, मेमोरी के ऐलोकेशन और डीऐलोकेशन के ट्रेस रिकॉर्ड किए जाते हैं. अगर फ़्रेम पॉइंटर मौजूद नहीं हैं, तो GWP-ASan स्टैक ट्रेस को अनवाइंड नहीं कर सकता.

arm64 डिवाइसों के लिए, फ़्रेम पॉइंटर डिफ़ॉल्ट रूप से चालू होते हैं. वहीं, arm32 डिवाइसों के लिए, ये डिफ़ॉल्ट रूप से बंद होते हैं. ऐप्लिकेशन के पास libc को कंट्रोल करने का ऐक्सेस नहीं होता. इसलिए, आम तौर पर GWP-ASan, 32-बिट के एक्ज़ीक्यूटेबल या ऐप्लिकेशन के लिए, मेमोरी के असाइनमेंट/डीअसाइनमेंट के ट्रेस इकट्ठा नहीं कर सकता. 64-बिट ऐप्लिकेशन को यह पक्का करना चाहिए कि उन्हें -fomit-frame-pointer का इस्तेमाल करके न बनाया गया हो, ताकि GWP-ASan, मेमोरी के बंटवारे और मेमोरी के बंटवारे को रद्द करने से जुड़ी स्टैक ट्रेस इकट्ठा कर सके.

सुरक्षा से जुड़े उल्लंघनों को दोहराना

GWP-ASan को उपयोगकर्ता के डिवाइसों पर, हीप मेमोरी की सुरक्षा से जुड़े उल्लंघनों का पता लगाने के लिए डिज़ाइन किया गया है. GWP-ASan, क्रैश के बारे में ज़्यादा से ज़्यादा जानकारी देता है. जैसे, उल्लंघन का ऐक्सेस ट्रेस, वजह बताने वाली स्ट्रिंग, और मेमोरी के असाइनमेंट/डीअसाइनमेंट के ट्रेस. हालांकि, यह पता लगाना अब भी मुश्किल हो सकता है कि उल्लंघन कैसे हुआ. माफ़ करें, गड़बड़ी का पता लगाने की प्रोसेस में संभावनाओं का इस्तेमाल किया जाता है. इसलिए, GWP-ASan की रिपोर्ट को अक्सर लोकल डिवाइस पर दोहराना मुश्किल होता है.

इन मामलों में, अगर गड़बड़ी का असर 64-बिट डिवाइसों पर पड़ता है, तो आपको HWAddressSanitizer (HWASan) का इस्तेमाल करना चाहिए. HWASan, स्टैक, हीप, और ग्लोबल पर मेमोरी सेफ़्टी के उल्लंघनों का पता लगाता है. HWASan के साथ ऐप्लिकेशन चलाने पर, आपको वही नतीजा मिल सकता है जो GWP-ASan से मिला था.

अगर एचडब्ल्यूएएसैन के तहत अपने ऐप्लिकेशन को चलाने से किसी बग की मुख्य वजह का पता नहीं चलता है, तो आपको उस कोड को फ़ज़ करने की कोशिश करनी चाहिए जिसमें समस्या आ रही है. 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 नेटिव क्रैश रिपोर्ट के बारे में ज़्यादा जानने के लिए, नेटिव क्रैश का पता लगाना लेख पढ़ें.