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

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 मेमोरी टैगिंग एक्सटेंशन (एमटीई) के साथ काम करने के लिए तैयार करें. ऐलोकेशन सैंपलिंग के तरीकों से, किसी उपयोगकर्ता की मौत से जुड़ी क्वेरी के लिए भी भरोसेमंद जानकारी मिलती है.

चालू होने के बाद, 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>

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. जब 'हैप का इस्तेमाल करने के बाद उसे खाली करना' या 'हैप बफ़र ओवरफ़्लो' गड़बड़ी का पता चलता है, तो यह गड़बड़ी क्रैश रिपोर्ट (टॉम्बस्टोन) में दिखती है. यह क्रैश रिपोर्ट, ओरिजनल GWP-ASan की तरह ही ActivityManager#getHistoricalProcessExitReasons API के ज़रिए उपलब्ध है.
  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, 32-बिट के ऐप्लिकेशन या प्रोग्राम के लिए, एलोकेशन/डिएलोकेशन ट्रेस इकट्ठा नहीं कर सकता. 64-बिट ऐप्लिकेशन को यह पक्का करना चाहिए कि वे -fomit-frame-pointer के साथ नहीं बनाए गए हों, ताकि GWP-ASan, स्टैक ट्रेस को इकट्ठा कर सके.

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

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

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