GWP-ASan

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ırıcı özelliğidir. Gayriresmî adı,"GWP-ASan Will Provide Allocation SANity" ifadesinin özyinelemeli kısaltmasıdır. HWASan veya Malloc Hata Ayıklama'nın aksine GWP-ASan, kaynak veya yeniden derleme gerektirmez (yani önceden derlenmiş öğelerle çalışır) ve hem 32 hem de 64 bit işlemlerde çalışır (ancak 32 bit kilitlenmelerde daha az hata ayıklama bilgisi bulunur). Bu konuda, uygulamanızda bu özelliği etkinleştirmek için yapmanız gerekenler açıklanmaktadır. GWP-ASan, Android 11 (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ı olması ve uygulamanızı ARM Bellek Etiketleme Uzantısı (MTE) desteğine hazırlaması için kendi uygulamanızda GWP-ASan'ı etkinleştirin. Tahsis örnekleme mekanizmaları, ölüm sorgularına karşı da güvenilirlik sağlar.

GWP-ASan etkinleştirildikten sonra, rastgele seçilen bir yığın ayırma alt kümesini yakalar ve bunları, tespit edilmesi zor yığın bellek bozulması hatalarını yakalayan özel bir bölgeye yerleştirir. Yeterli sayıda kullanıcı varsa bu düşük örnekleme hızı bile normal testlerde bulunamayan yığın bellek güvenliği hatalarını bulur. Örneğin, GWP-ASan, Chrome tarayıcıda önemli sayıda hata bulmuştur (bunların çoğu hâlâ kısıtlı görünüm altındadır).

GWP-ASan, müdahale ettiği tüm ayırmalar hakkında ek bilgiler toplar. Bu bilgiler, GWP-ASan bir bellek güvenliği ihlali tespit ettiğinde kullanılabilir ve otomatik olarak yerel kilitlenme raporuna yerleştirilir. Bu bilgiler, hata ayıklama işlemine önemli ölçüde yardımcı olabilir (Örnek bölümüne bakın).

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

Uygulamanızı kaydetme

GWP-ASan, uygulama manifestinde android:gwpAsanMode etiketi kullanılarak uygulamalar tarafından işlem düzeyinde 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 dışı uygulamalar için varsayılan ayardı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 etkindir.

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

    1. İşletim sistemi, GWP-ASan işlemleri için sabit miktarda RAM 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 rastgele seçilen 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 hatayla ilgili ek bilgiler sağlar.

Uygulamanız için GWP-ASan'ı 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'ı açıkça etkinleştirmeyi seçen veya kapsamı dışında kalmayı seçen işlemleri kullanarak etkinlikleri ve hizmetleri hedefleyebilirsiniz. Aşağıdaki örneğe 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ümler, kurtarılabilir GWP-ASan'ı destekler. Bu özellik, geliştiricilerin kullanıcı deneyimini olumsuz etkilemeden üretimde yığın arabellek taşması ve yığın boşaltıldıktan sonra kullanım hatalarını bulmasına yardımcı olur. android:gwpAsanMode, AndroidManifest.xml içinde belirtilmediğinde uygulama, kurtarılabilir 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şlatıldığında değil, yalnızca uygulama başlatma işlemlerinin yaklaşık% 1'inde etkinleştirilir.
  2. Serbest bırakıldıktan sonra yığın kullanımı veya yığın arabelleği taşması hatası algılandığında bu hata, kilitlenme raporunda (tombstone) görünür. Bu kilitlenme raporuna, orijinal GWP-ASan'da olduğu gibi ActivityManager#getHistoricalProcessExitReasons API'si üzerinden erişilebilir.
  3. Kurtarılabilir GWP-ASan, kilitlenme raporunu döküm haline getirdikten sonra çıkmak yerine bellek bozulmasının gerçekleşmesine izin verir ve uygulama çalışmaya devam eder. İşlem her zamanki gibi devam etse de uygulamanın davranışı artık belirtilmez. Bellek bozulması nedeniyle uygulama gelecekte herhangi bir zamanda kilitlenebilir veya kullanıcı tarafından fark edilebilecek bir etki olmadan çalışmaya devam edebilir.
  4. Kurtarılabilir GWP-ASan, kilitlenme raporu dökümü alındıktan sonra devre dışı bırakılır. Bu nedenle, bir uygulama her başlatıldığında yalnızca bir adet kurtarılabilir GWP-ASan raporu alabilir.
  5. Uygulamada özel bir sinyal işleyici yüklüyse 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ındaki gerçek bellek bozulması örneklerini gösterdiğinden, Kurtarılabilir GWP-ASan tarafından tespit edilen hataları öncelikli olarak değerlendirip düzeltmenizi önemle tavsiye ederiz.

Geliştirici desteği

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

Tahsis/tahsis kaldırma izleri eksik

Tahsis/tahsis kaldırma kareleri eksik gibi görünen yerel bir kilitlenmeyi teşhis ediyorsanız uygulamanızda kare işaretçileri eksik olabilir. GWP-ASan, performans nedeniyle ayırma ve ayırmayı kaldırma izlerini kaydetmek için çerçeve işaretçilerini kullanır ve bunlar yoksa yığın izlemeyi geri alamaz.

Kare işaretçileri, arm64 cihazlarda varsayılan olarak açık, arm32 cihazlarda ise varsayılan olarak kapalıdır. Uygulamalar libc üzerinde kontrole sahip olmadığından GWP-ASan'ın 32 bit yürütülebilir dosyalar veya uygulamalar için ayırma/ayırmayı kaldırma izlerini toplaması (genel olarak) mümkün değildir. 64 bit uygulamalar, GWP-ASan'ın ayırma ve ayırmayı kaldırma yığın izlemelerini toplayabilmesi için -fomit-frame-pointer ile derlenmediğinden emin olmalıdır.

Güvenlik ihlallerini tekrarlama

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 (ihlal erişim izi, neden dizesi ve ayırma/ayırmayı kaldırma izleri) sağlar ancak ihlalin nasıl gerçekleştiğini anlamak yine de zor olabilir. Maalesef hata algılama olasılığa dayalı olduğundan GWP-ASan raporlarını yerel bir cihazda yeniden oluşturmak genellikle zordur.

Bu gibi durumlarda, hata 64 bit cihazları etkiliyorsa HWAddressSanitizer (HWASan) kullanmanız gerekir. HWASan; yığın, yığın ve global değişkenlerdeki bellek güvenliği ihlallerini güvenilir bir şekilde algılar. Uygulamanızı HWASan ile çalıştırmak, GWP-ASan tarafından bildirilen sonucun aynısını güvenilir bir şekilde yeniden oluşturabilir.

Uygulamanızı HWASan altında çalıştırmanın bir hatanın temel nedenini bulmak için yeterli olmadığı durumlarda söz konusu kodu fuzz etmeyi denemeniz gerekir. Fuzzing çalışmalarınızı, temel kod sağlığı sorunlarını güvenilir bir şekilde tespit edip ortaya çıkarabilen GWP-ASan raporundaki bilgilere göre hedefleyebilirsiniz.

Örnek

Bu örnek yerel kodda, serbest bırakıldıktan sonra yığın kullanımı hatası vardır:

#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 kodun kullanıldığı bir test çalıştırmasında GWP-ASan, geçersiz 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 izlemeleri 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.