GWP-ASan

GWP-ASan, kullanımdan sonra kullanım ve heap-buffer-overflow hatalarını bulmaya yardımcı olan yerel bir bellek ayırıcı özelliğidir. Resmi olmayan adı, yinelenen bir kısaltma olan "GWP-ASan Will Provide Allocation SANity"dir. HWASan veya Malloc Debug'ın aksine GWP-ASan kaynak veya yeniden derleme gerektirmez (yani önceden derlenmişlerle çalışır) ve hem 32 bit hem de 64 bit işlemlerde çalışır (ancak 32 bit kilitlenmelerde daha az hata ayıklama bilgisi bulunur). Bu konuda, bu özelliği uygulamanızda etkinleştirmek için yapmanız gereken işlemler özetlenmektedir. GWP-ASan, Android 11 (API düzeyi 30) veya sonraki sürümleri hedefleyen uygulamalarda kullanılabilir.

Genel Bakış

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

Etkinleştirildikten sonra GWP-ASan, rastgele seçilen bir yığın tahsis alt kümesini durdurur 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 oranı bile, normal testlerde bulunmayan yığın bellek güvenlik hatalarını bulur. Örneğin, GWP-ASan, Chrome tarayıcıda önemli sayıda hata tespit etti (bunlardan çoğu hâlâ kısıtlı görünümde).

GWP-ASan, müdahale ettiği tüm ayırmalar hakkında ek bilgi toplar. GWP-ASan bir bellek güvenliği ihlalini algıladığında bu bilgiler kullanılabilir ve otomatik olarak yerel kilitlenme raporuna yerleştirilir. Bu, hata ayıklamada önemli ölçüde yardımcı olabilir (Örnek'e bakın).

GWP-ASan, önemli bir CPU yükü oluşturmayacak şekilde tasarlanmıştır. GWP-ASan etkinleştirildiğinde küçük ve sabit bir RAM yükü oluşturur. Bu yükü Android sistemi belirler ve şu anda etkilenen her işlem için yaklaşık 70 kibibayttır (KiB).

Uygulamanızı dahil edin

GWP-ASan, uygulama manifest'inde android:gwpAsanMode etiketi kullanılarak işlem başına düzeyde uygulamalar tarafından etkinleştirilebilir. Aşağıdaki seçenekler desteklenir:

  • Her zaman devre dışı (android:gwpAsanMode="never"): Bu ayar, GWP-ASan'ı uygulamanızda 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. GWP-ASan aşağıdakileri içerir:

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

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

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

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

GWP-ASan'ı uygulamanız için dünya genelinde etkinleştirmek üzere AndroidManifest.xml dosyanıza aşağıdakileri ekleyin:

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

Ayrıca GWP-ASan, uygulamanızın belirli alt süreçleri 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ğıdakine 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, 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ı bulmalarına yardımcı olan Kurtarılabilen GWP-ASan'ı destekler. Bir AndroidManifest.xml içinde android:gwpAsanMode belirtilmediğinde uygulama, Kurtarılabilen GWP-ASan'ı kullanır.

Kurtarılabilir GWP-ASan, temel GWP-ASan'dan şu bakımlardan farklıdır:

  1. Kurtarılabilen GWP-ASan, her uygulama lansmanında değil, yalnızca uygulama lansmanlarının yaklaşık %1'inde etkinleştirilir.
  2. Bir yığının serbest bırakıldıktan sonra kullanılması veya yığın arabelleğinin taşması hatası algılandığında bu hata kilitlenme raporunda (mezar taşı) görünür. Bu kilitlenme raporu, orijinal GWP-ASan ile aynı şekilde ActivityManager#getHistoricalProcessExitReasons API'si üzerinden kullanılabilir.
  3. Kurtarılabilir GWP-ASan, kilitlenme raporunun dökümü oluşturulduktan 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 herhangi bir noktada kilitlenebilir veya kullanıcı tarafından fark edilebilecek herhangi bir etki olmadan çalışmaya devam edebilir.
  4. Kurtarılabilen GWP-ASan, kilitlenme raporu döküldükten sonra devre dışı bırakılır. Bu nedenle, bir uygulama her başlatıldığında yalnızca tek bir kurtarılabilir GWP-ASan raporu alabilir.
  5. Uygulamaya özel bir sinyal işleyici yüklüyse bu işleyici, kurtarılabilir GWP-ASan hatasını gösteren bir SIGSEGV sinyali için hiçbir zaman çağrılmaz.

Kurtarılabilen GWP-ASan kilitlenmeleri, son kullanıcı cihazlarında gerçek bellek bozulması örneklerini gösterdiğinden, Kurtarılabilen GWP-ASan tarafından tanımlanan hataları öncelikli olarak tespit edip 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 ele alınacağı özetlenmiştir.

Ayırma/ayırma sonlandırma izlemeleri eksik

Ayırma/ayırma çerçeveleri eksik görünen bir yerel kilitlenmeyi teşhis ediyorsanız uygulamanızda muhtemelen çerçeve işaretçileri eksiktir. GWP-ASan, performans nedeniyle ayırma ve ayırma sonlandırma izlerini kaydetmek için çerçeve işaretçileri kullanır ve bunlar mevcut değilse yığın izlemeyi çözemez.

Çerçeve işaretçileri, arm64 cihazlar için varsayılan olarak açık, arm32 cihazlar için 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 ayırma/ayırma sonlandırma izlerini toplaması (genel olarak) mümkün değildir. 64 bit uygulamalar, GWP-ASan'ın ayırma ve ayırma yığın izlemelerini toplayabilmesi için -fomit-frame-pointer ile oluşturulmamalıdır.

Güvenlik ihlallerini yeniden üretme

GWP-ASan, kullanıcı cihazlarındaki yığın bellek güvenlik ihlallerini yakalamak için tasarlanmıştır. GWP-ASan, kilitlenmeyle ilgili mümkün olduğunca fazla bağlam bilgisi (ihlale ait erişim izleme, neden dizesi ve ayırma/ayırma sonlandırma izlemeleri) 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ın yerel bir cihazda yeniden oluşturulması genellikle zordur.

Bu durumlarda, hata 64 bit cihazları etkiliyorsa HWAddressSanitizer'ı (HWASan) kullanmanız gerekir. HWASan, yığın, yığın alanı ve genel alanlarda bellek güvenliği ihlallerini güvenilir bir şekilde algılar. Uygulamanızı HWASan ile çalıştırmak, GWP-ASan tarafından raporlanan sonucu güvenilir bir şekilde yeniden üretebilir.

Uygulamanızı HWASan altında çalıştırmanın, hatanın temel nedenini bulmak için yeterli olmadığı durumlarda söz konusu kodu fuzz yapmayı denemeniz gerekir. Kodda bulunan sağlık sorunlarını güvenilir bir şekilde tespit edip ortaya çıkarabilen GWP-ASan raporundaki bilgilere göre kodunuzu ayıklama çalışmalarınıza yön verebilirsiniz.

Örnek

Bu örnek yerel kodda, boşaltı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 kodu kullanan bir test çalıştırması için, GWP-ASan yasa dışı kullanımı başarılı bir şekilde tespit etmiş ve aşağıdaki kilitlenme raporunu tetiklemiştir. GWP-ASan; kilitlenme türü, ayırma meta verileri, ilişkili ayırma ve dağıtım yığın izlemeleri hakkında bilgi sağlayarak raporu otomatik olarak geliştirmiştir.

*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
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 belgelerini inceleyin. Android yerel kilitlenme raporları hakkında daha fazla bilgi edinmek için Yerel Kilitlenmeleri Teşhis Etme başlıklı makaleyi inceleyin.