GWP-ASan

Bellek hatası ayıklama ve azaltma başlıklı makaleyi inceleyin.Kurtarılabilir GWP-ASan'ı kullanır.

GWP-ASan, boşaltıldıktan sonra kullanım ve yığın arabellek taşması hatalarını bulmaya yardımcı olan yerel bir bellek ayırma özelliğidir. Gayri resmi adı,"GWP-ASan Will Provide Allocation SANity" şeklinde özyinelemeli bir kısaltmadır. HWASan veya Malloc Debug'dan farklı olarak GWP-ASan, kaynak veya yeniden derleme gerektirmez (yani önceden oluşturulmuş öğelerle çalışır) ve hem 32 bit hem de 64 bit işlemlerinde çalışır (ancak 32 bit kilitlenmelerinde daha az hata ayıklama bilgisi bulunur). Bu konuda, uygulamanızda bu özelliği etkinleştirmek için yapmanız gereken işlemler özetlenmektedir. GWP-ASan, Android 11'i (API düzeyi 30) veya sonraki sürümleri hedefleyen uygulamalarda kullanılabilir.

Genel Bakış

GWP-ASan, işlem başlatıldığında (veya zygote çatallandığında) rastgele seçilen bazı sistem uygulamalarında ve platform yürütülebilir dosyalarında etkinleştirilir. Bellekle ilgili hataları bulmanıza yardımcı olmak ve uygulamanızı ARM Bellek Etiketleme Uzantısı (MTE) desteğine hazırlamak için kendi uygulamanızda GWP-ASan'ı etkinleştirin. Ayrım örnekleme mekanizmaları, ölüm sorgularına karşı güvenilirlik de sağlar.

Etkinleştirildikten sonra GWP-ASan, yığın ayırmalarının rastgele seçilmiş bir alt kümesini yakalar ve bunları, tespit edilmesi zor olan yığın bellek bozulması hatalarını yakalayan özel bir bölgeye yerleştirir. Yeterli sayıda kullanıcı olduğunda bu düşük örnekleme oranı bile normal testlerle bulunamayan yığın belleği güvenliği hatalarını bulur. Örneğin, GWP-ASan, Chrome tarayıcıda önemli sayıda hata buldu (bunların çoğu hâlâ kısıtlı görünümde).

GWP-ASan, yakaladığı tüm ayırmalar hakkında ek bilgiler toplar. Bu bilgiler, GWP-ASan bir bellek güvenliği ihlali algıladığında kullanılabilir ve yerel kilitlenme raporuna otomatik olarak yerleştirilir. Bu bilgiler, hata ayıklama sürecinde önemli ölçüde yardımcı olabilir (bkz. Örnek).

GWP-ASan, önemli bir CPU ek yüküne neden olmayacak şekilde tasarlanmıştır. GWP-ASan etkinleştirildiğinde küçük ve sabit bir RAM ek yükü getirir. Bu ek yük, Android sistemi tarafından belirlenir ve etkilenen her işlem için şu anda yaklaşık 70 kibibayttır (KiB).

Uygulamanızı etkinleştirme

GWP-ASan, uygulama manifestinde android:gwpAsanMode etiketi kullanılarak uygulamalar tarafından süreç bazında etkinleştirilebilir. Aşağıdaki seçenekler desteklenir:

  • Her zaman devre dışı (android:gwpAsanMode="never"): Bu ayar, uygulamanızda GWP-ASan'ı tamamen devre dışı bırakır ve sistem uygulamaları dışındaki uygulamalar için varsayılandır.

  • Varsayılan (android:gwpAsanMode="default" veya belirtilmemiş): Android 13 (API düzeyi 33) ve önceki sürümler: GWP-ASan devre dışıdır. Android 14 (API düzeyi 34) ve sonraki sürümler: Kurtarılabilir GWP-ASan etkinleştirilir.

  • Her zaman etkin (android:gwpAsanMode="always"): Bu ayar, uygulamanızda GWP-ASan'ı etkinleştirir. Bu özellik şunları içerir:

    1. İşletim sistemi, GWP-ASan işlemleri için sabit bir RAM miktarı ayırır. Bu miktar, etkilenen her işlem için yaklaşık 70 KiB'dir. (Uygulamanız bellek kullanımındaki artışlara karşı kritik düzeyde hassas değilse GWP-ASan'ı etkinleştirin.)

    2. GWP-ASan, yığın ayırmalarının rastgele seçilmiş bir alt kümesini yakalar ve bunları, bellek güvenliği ihlallerini güvenilir bir şekilde algılayan özel bir bölgeye yerleştirir.

    3. Özel bölgede bellek güvenliği ihlali meydana geldiğinde GWP-ASan işlemi sonlandırır.

    4. GWP-ASan, kilitlenme raporundaki hata hakkında ek bilgiler sağlar.

GWP-ASan'ı uygulamanız için genel olarak etkinleştirmek istiyorsanız AndroidManifest.xml dosyanıza aşağıdakileri ekleyin:

<application android:gwpAsanMode="always">
  ...
</application>

Ayrıca, GWP-ASan, uygulamanızın belirli alt işlemleri için açıkça etkinleştirilebilir veya devre dışı bırakılabilir. GWP-ASan'ın açıkça etkinleştirildiği veya devre dışı bırakıldığı işlemleri kullanarak etkinlikleri ve hizmetleri hedefleyebilirsiniz. Örnek için aşağıya bakın:

<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>

Kurtarılabilir GWP-ASan

Android 14 (API düzeyi 34) ve sonraki sürümlerde, geliştiricilerin kullanıcı deneyimini düşürmeden üretimde yığın arabellek taşması ve yığın boşaltıldıktan sonra kullanım hatalarını bulmasına yardımcı olan Kurtarılabilir GWP-ASan desteklenir. android:gwpAsanMode, AndroidManifest.xml içinde belirtilmediğinde uygulama, Recoverable GWP-ASan'ı kullanır.

Kurtarılabilir GWP-ASan, temel GWP-ASan'dan şu yönleriyle farklıdır:

  1. Kurtarılabilir GWP-ASan, her uygulama başlatma işleminde değil, yalnızca yaklaşık% 1'inde etkinleştirilir.
  2. Bir heap-use-after-free veya heap-buffer-overflow hatası algılandığında bu hata, kilitlenme raporunda (tombstone) görünür. Bu kilitlenme raporu, orijinal GWP-ASan'da olduğu gibi ActivityManager#getHistoricalProcessExitReasons API'si üzerinden kullanılabilir.
  3. Kurtarılabilir GWP-ASan, kilitlenme raporu dökümü yapıldıktan sonra çıkmak yerine bellek bozulmasına izin verir ve uygulama çalışmaya devam eder. İşlem normal şekilde devam edebilir ancak uygulamanın davranışı artık belirtilmez. Bellek bozulması nedeniyle uygulama gelecekte rastgele bir noktada kilitlenebilir veya kullanıcı tarafından görülebilen herhangi bir etki olmadan devam edebilir.
  4. Kilitlenme raporu dökümü yapıldıktan sonra kurtarılabilir GWP-ASan devre dışı bırakılır. Bu nedenle, bir uygulama, her uygulama başlatma işleminde yalnızca tek bir kurtarılabilir GWP-ASan raporu alabilir.
  5. Uygulamaya özel bir sinyal işleyici yüklenmişse bu işleyici, kurtarılabilir bir GWP-ASan hatasını gösteren SIGSEGV sinyali için hiçbir zaman çağrılmaz.

Kurtarılabilir GWP-ASan kilitlenmeleri, son kullanıcı cihazlarında bellek bozulmasının gerçek örneklerini gösterdiğinden, Kurtarılabilir GWP-ASan tarafından tanımlanan hataları yüksek öncelik vererek ayıklamanızı ve düzeltmenizi önemle tavsiye ederiz.

Geliştirici desteği

Bu bölümlerde, GWP-ASan kullanılırken ortaya çıkabilecek sorunlar ve bu sorunların nasıl giderileceği açıklanmaktadır.

Ayırma/ayırma izleri eksik

Yerel bir kilitlenmeyi teşhis ediyorsanız ve bu kilitlenmede tahsis/tahsis kaldırma çerçevelerinin eksik olduğu görülüyorsa uygulamanızda büyük olasılıkla çerçeve işaretçileri eksiktir. GWP-ASan, performans nedeniyle ayırma ve ayırmayı kaldırma izlerini kaydetmek için çerçeve işaretçilerini kullanır ve bunlar mevcut değilse yığın izini geri saramaz.

Çerçeve işaretçileri, arm64 cihazlarda varsayılan olarak açık, arm32 cihazlarda ise varsayılan olarak kapalıdır. Uygulamalar libc üzerinde kontrol sahibi olmadığından, GWP-ASan'ın 32 bitlik yürütülebilir dosyalar veya uygulamalar için genel olarak bellek ayırma/ayırma izlerini toplaması mümkün değildir. 64 bit uygulamalar, GWP-ASan'ın ayırma ve ayırmayı kaldırma yığın izlerini toplayabilmesi için -fomit-frame-pointer ile oluşturulmadığından emin olmalıdır.

Güvenlik ihlallerini yeniden üretme

GWP-ASan, kullanıcı cihazlarındaki yığın bellek güvenliği ihlallerini yakalamak için tasarlanmıştır. GWP-ASan, kilitlenme hakkında mümkün olduğunca fazla bağlam bilgisi (ihlalin izine, neden dizesine ve ayırma/ayırma izlerine erişim) sağlar ancak ihlalin nasıl gerçekleştiğini anlamak yine de zor olabilir. Hata algılama olasılığa dayalı olduğundan GWP-ASan raporlarının yerel bir cihazda yeniden üretilmesi genellikle zordur.

Bu gibi durumlarda hata 64 bit cihazları etkiliyorsa HWAddressSanitizer (HWASan) kullanmanız gerekir. HWASan, yığın, heap ve genel değişkenlerde bellek güvenliği ihlallerini güvenilir bir şekilde algılar. Uygulamanızı HWASan ile çalıştırmak, GWP-ASan tarafından bildirilen sonucun güvenilir bir şekilde yeniden üretilmesini sağlayabilir.

Uygulamanızı HWASan altında çalıştırmanın bir hatanın temel nedenini bulmak için yeterli olmadığı durumlarda, söz konusu kodu bulanıklaştırmayı denemelisiniz. GWP-ASan raporundaki bilgilere göre bulanıklaştırma çalışmalarınızı hedefleyebilirsiniz. Bu rapor, temel kod sağlığı sorunlarını güvenilir bir şekilde tespit edip ortaya çıkarabilir.

Örnek

Bu örnek yerel kodda yığınla ilgili bir use-after-free hatası var:

#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));
}

Yukarıdaki örnek kod kullanılarak yapılan bir test çalıştırmasında GWP-ASan, yasa dışı kullanımı başarıyla yakaladı ve aşağıdaki kilitlenme raporunu tetikledi. GWP-ASan, kilitlenme türü, ayırma meta verileri ve ilişkili ayırma ve ayırmayı kaldırma yığını izleri hakkında bilgi sağlayarak raporu otomatik olarak iyileştirdi.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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)
      ...

Daha fazla bilgi

GWP-ASan'ın uygulama ayrıntıları hakkında daha fazla bilgi edinmek için LLVM belgelerine bakın. Android yerel kilitlenme raporları hakkında daha fazla bilgi edinmek için Yerel Kilitlenmeleri Teşhis Etme başlıklı makaleyi inceleyin.