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. Gayri resmî adı tekrarlayan bir kısaltmadır,"GWP-ASan Will Provide Ayer SANity". HWASan veya Malloc Debug'dan farklı olarak, GWP-ASan kaynak veya yeniden derleme gerektirmez (yani, önceden oluşturulmuş öğelerle çalışır) ve hem 32 hem de 64 bit işlemlerde çalışır (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, işlem başlatıldığında (veya zigot çatallandığında) rastgele seçilen bazı sistem uygulamalarında ve platform yürütülebilirlerinde etkinleştirilir. Bellekle ilgili hataları bulmanıza ve uygulamanızı ARM Bellek Etiketleme Uzantısı (MTE) desteğine hazırlamanıza yardımcı olması için kendi uygulamanızda GWP-ASan'ı etkinleştirin. Tahsis örnekleme mekanizmaları, ölüm sorgularına karşı da güvenilirlik sağlar.

Etkinleştirildiğinde GWP-ASan, yığın ayırmaların rastgele seçilmiş bir alt kümesine müdahale eder ve bunları, algılanması zor yığın bellek bozulması hatalarını yakalayan özel bir bölgeye yerleştirir. Yeterli sayıda kullanıcı düşünüldüğünde, bu düşük örnekleme hızı bile düzenli testlerde bulunamayan yığın bellek güvenliği hatalarını bulur. Örneğin, GWP-ASan, Chrome tarayıcısında önemli sayıda hata tespit etmiştir (bunların birçoğu hâlâ kısıtlanmış görünümdedir).

GWP-ASan, müdahale ettiği tüm ayırmalar hakkında ek bilgi 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, hata ayıklamaya önemli ölçüde yardımcı olabilir (Örnek bölümüne bakın).

GWP-ASan, önemli bir CPU ek yükü oluşturmayacak şekilde tasarlanmıştır. GWP-ASan, etkinleştirildiğinde küçük, sabit bir RAM ek yükü sunar. 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ı dahil edin

GWP-ASan, uygulama manifestindeki android:gwpAsanMode etiketi kullanılarak uygulamalar tarafından işlem başına düzeyde 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 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 son derece duyarlı değilse GWP-ASan'ı etkinleştirin.)

    2. GWP-ASan, rastgele seçilmiş yığın ayırmaların alt kümesini yakalar ve bunları bellek güvenliği ihlallerini güvenilir ş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'i uygulamanızda genel olarak etkinleştirmek için AndroidManifest.xml dosyanıza şunu 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ştirilmiş veya devre dışı bırakılmış olduğu işlemleri kullanarak etkinlikleri ve hizmetleri hedefleyebilirsiniz. Aşağıda bir örnek görebilirsiniz:

<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 olumsuz etkilemeden üretimde yığın arabellek taşması ve boşaltıldıktan sonra yığın kullanımı hataları bulmasına yardımcı olan Kurtarılabilir GWP-ASan'ı destekler. AndroidManifest.xml öğesinde android:gwpAsanMode belirtilmediğinde uygulama, Kurtarılabilir GWP-ASan'ı kullanır.

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

  1. Kurtarılabilir GWP-ASan, her uygulama lansmanı yerine, uygulama lansmanlarının yalnızca yaklaşık% 1'inde etkinleştirilir.
  2. Boş olduğunda yığın kullanımı veya yığın arabellek 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 üzerinden ulaşı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. Süreç her zamanki gibi devam edebilir ancak uygulamanın davranışı artık belirtilmemiştir. Bellek bozulması nedeniyle uygulama ileride rastgele bir noktada kilitlenebilir veya kullanıcı tarafından görülebilen herhangi bir etki olmadan devam edebilir.
  4. Kilitlenme raporu dökümünden sonra kurtarılabilir GWP-ASan devre dışı bırakılır. Bu nedenle, bir uygulama her uygulama lansmanı için yalnızca tek bir Kurtarılabilir GWP-ASan raporu alabilir.
  5. Uygulamada özel bir sinyal işleyici yüklüyse hiçbir zaman Kurtarılabilir GWP-ASan hatasını gösteren bir SIGSEGV sinyali çağrılmaz.

Kurtarılabilir GWP-ASan kilitlenmeleri, son kullanıcı cihazlarında gerçek bellek bozulması örneklerini gösterdiğinden, Kurtarılabilir GWP-ASan tarafından belirlenen ve yüksek önceliğe sahip hataların önceliğini belirleyip düzeltmenizi önemle tavsiye ederiz.

Geliştirici desteği

Bu bölümlerde, GWP-ASan'ı kullanırken ortaya çıkabilecek sorunlar ve bunların nasıl ele alınacağı açıklanmaktadır.

Ayırma/ayırma izleri 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, performansla ilgili nedenlerle ayırma ve dağıtım izlerini kaydetmek için kare işaretçileri kullanır. Yığın izleme mevcut değilse yığın izlemeyi geri döndüremez.

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

Güvenlik ihlallerini tekrar oluşturma

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 bilgi (ihlalin erişim izlemesi, neden dizesi ve ayırma/ayırma izleri) sağlar, ancak ihlalin nasıl oluştuğunu tahmin etmek yine de zor olabilir. Hata algılama işlemi olası olduğundan, GWP-ASan raporlarının yerel bir cihazda yeniden oluşturulması maalesef genellikle zordur.

Bu gibi durumlarda, hata 64 bit cihazları etkiliyorsa HWAddressSanitizer (HWASan) kullanmanız gerekir. HWASan; yığın, yığın ve global uygulamalarda bellek güvenliği ihlallerini güvenilir bir şekilde tespit eder. Uygulamanızı HWASan ile çalıştırdığınızda, GWP-ASan tarafından bildirilen sonucu güvenilir bir şekilde yeniden oluşturabilirsiniz.

Uygulamanızı HWASan altında çalıştırmanın, bir hataya kök neden olmak için yeterli olmadığı durumlarda, söz konusu kodu bulanıklaştırmayı denemeniz gerekir. Bulanıklaştırma çalışmalarınızı GWP-ASan raporundaki bilgilere göre hedefleyebilirsiniz. Bu bilgiler, temel kod durumu sorunlarını güvenilir bir şekilde tespit edip ortaya çıkarabilir.

Ö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 bölümüne bakın.