GWP-ASan

GWP-ASan adalah fitur pengalokasi memori native yang membantu menemukan bug use-after-free dan heap-buffer-overflow. Nama informalnya adalah singkatan rekursif dari "GWP-ASan Will Provide Allocation SANity". Tidak seperti HWASan atau Malloc Debug, GWP-ASan tidak memerlukan sumber atau rekompilasi (yang kompatibel dengan fitur bawaan), dan sesuai untuk pemrosesan 32 bit. Topik ini menguraikan tindakan yang perlu Anda lakukan untuk mengaktifkan fitur ini di aplikasi Anda. GWP-ASan tersedia di aplikasi yang menargetkan Android 11 (API level 'R') atau yang lebih tinggi.

Ringkasan

GWP-ASan diaktifkan di beberapa executable platform dan aplikasi yang dipilih secara acak saat proses dimulai (atau saat mengambil zygote). Aktifkan GWP-ASan di aplikasi Anda untuk membantu menemukan bug terkait memori, dan menyiapkan aplikasi untuk dukungan Ekstensi Pemberian Tag Memori ARM (MTE). Mekanisme pengambilan sampel alokasi juga memberikan keandalan terhadap kueri yang error.

Setelah diaktifkan, GWP-ASan mengintersep subset alokasi heap yang dipilih secara acak, dan menempatkannya ke region khusus yang menangkap bug kerusakan memori heap yang sulit dideteksi. Dengan memperhitungkan banyaknya pengguna, frekuensi pengambilan sampel rendah ini akan menemukan bug keamanan memori heap yang tidak ditemukan melalui pengujian reguler. Misalnya, GWP-ASan menemukan sejumlah besar bug di browser Chrome (sebagian besar di antaranya masih dalam tampilan terbatas).

GWP-ASan mengumpulkan informasi tambahan tentang semua alokasi yang diintersepnya. Informasi ini tersedia saat GWP-ASan mendeteksi pelanggaran keamanan memori dan otomatis ditempatkan ke dalam laporan masalah pada native code, yang dapat membantu proses debug secara signifikan (lihat contoh).

GWP-ASan dirancang untuk tidak menimbulkan overhead CPU yang signifikan. GWP-ASan memperkenalkan overhead RAM yang kecil dan berukuran tetap ketika diaktifkan. Overhead ini ditentukan oleh sistem Android dan saat ini berukuran sekitar 70 kibibyte (KiB) untuk setiap proses yang terpengaruh.

Mengikutsertakan aplikasi Anda

GWP-ASan dapat diaktifkan oleh aplikasi pada level per proses menggunakan tag android:gwpAsanMode dalam manifes aplikasi. Opsi-opsi berikut didukung:

  • Selalu nonaktif (android:gwpAsanMode="never"): Setelan ini sepenuhnya menonaktifkan GWP-ASan di aplikasi Anda dan merupakan setelan default untuk aplikasi non-sistem.

  • Selalu aktif (android:gwpAsanMode="always"): Setelan ini mengaktifkan GWP-ASan di aplikasi Anda, yang mencakup hal berikut:

    1. Sistem operasi menggunakan jumlah RAM yang berukuran tetap untuk operasi GWP-ASan, yaitu sekitar 70 KiB untuk setiap proses yang terpengaruh. (Aktifkan GWP-ASan jika aplikasi Anda tidak terlalu mempermasalahkan peningkatan penggunaan memori.)

    2. GWP-ASan mengintersep subset alokasi heap yang dipilih secara acak dan menempatkannya ke region khusus yang mampu mendeteksi pelanggaran keamanan memori.

    3. Saat pelanggaran keamanan memori terjadi di region khusus, GWP-ASan akan menghentikan prosesnya.

    4. GWP-ASan memberikan informasi tambahan terkait masalah dalam laporan kerusakan.

Untuk mengaktifkan GWP-ASan secara global untuk aplikasi Anda, tambahkan baris berikut ke file AndroidManifest.xml Anda:

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

Selain itu, GWP-ASan dapat diaktifkan atau dinonaktifkan secara eksplisit untuk subproses aplikasi tertentu Anda. Anda dapat menargetkan aktivitas dan layanan menggunakan proses yang diikutsertakan atau tidak diikutsertakan secara eksplisit dalam GWP-ASan. Lihat contoh berikut ini:

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

Dukungan developer

Bagian ini menguraikan masalah yang mungkin terjadi saat menggunakan GWP-ASan dan cara mengatasinya.

Rekaman aktivitas alokasi/dealokasi tidak ada

Jika Anda mendiagnosis masalah pada native code yang tampaknya tidak memiliki frame alokasi/dealokasi, kemungkinan aplikasi Anda tidak memiliki pointer frame. GWP-ASan menggunakan pointer frame untuk mencatat alokasi dan dealokasi rekaman aktivitas untuk alasan performa, dan tidak dapat melepas pelacakan tumpukan jika frame tersebut tidak ada. Anda harus mengompilasi ulang kode Anda dengan mengaktifkan pointer frame (-fno-omit-frame-pointer) untuk mendapatkan alokasi dan dealokasi pelacakan tumpukan. Pointer frame aktif secara default untuk perangkat arm64, dan dinonaktifkan secara default untuk perangkat arm32.

Mereproduksi pelanggaran keamanan

GWP-ASan dirancang untuk menangkap pelanggaran keamanan memori heap pada perangkat pengguna. GWP-ASan memberikan sebanyak mungkin konteks tentang error (akses terhadap rekaman aktivitas, string penyebab, dan alokasi/dealokasi rekaman aktivitas), tetapi mungkin masih sulit untuk menyimpulkan bagaimana pelanggaran tersebut terjadi. Sayangnya, karena deteksi bug bersifat probabilistik, laporan GWP-ASan sering kali sulit direproduksi di perangkat lokal.

Dalam hal ini, jika bug memengaruhi perangkat 64 bit, Anda harus menggunakan HWAddressSanitizer (HWASan). HWASan mendeteksi pelanggaran keamanan memori dengan andal pada stack, heap, dan secara global. Menjalankan aplikasi Anda dengan HWASan dapat secara andal mereproduksi hasil yang sama dengan yang dilaporkan oleh GWP-ASan.

Dalam situasi ketika menjalankan aplikasi dengan HWASan ternyata tidak cukup untuk mengidentifikasi akar masalah bug, sebaiknya coba samarkan kode yang dimaksud. Anda dapat menargetkan upaya penyamaran berdasarkan informasi pada laporan GWP-ASan, yang dapat mendeteksi dan mengungkapkan masalah kesehatan kode yang mendasarinya dengan andal.

Contoh

Contoh kode native ini memiliki bug use-after-free heap:

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

Untuk pengujian yang menggunakan kode contoh di atas, GWP-ASan berhasil menangkap penggunaan ilegal dan memicu laporan kerusakan di bawah. GWP-ASan otomatis menyempurnakan laporan dengan memberikan informasi tentang jenis error, metadata alokasi, serta alokasi dan dealokasi pelacakan tumpukan.

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

Informasi selengkapnya

Untuk mempelajari lebih lanjut detail implementasi GWP-ASan, lihat Dokumentasi LLVM. Untuk mempelajari lebih lanjut laporan masalah pada native code, lihat Mendiagnosis Masalah pada Native Code.