Ponsel Anda dapat mendeteksi korupsi memori di produksi. Tidak dengan instrumentasi penuh yang Anda jalankan di CI, dan tidak pada setiap alokasi. Namun hardware di saku Anda telah mengirimkan primitif yang diperlukan selama beberapa tahun sekarang, dan semakin banyak aplikasi produksi yang diam-diam mengaktifkannya.
Versi singkatnya: AddressSanitizer terlalu mahal untuk produksi. ARM Memory Tagging Extension tidak. Dan jika Anda belum dapat mengandalkan MTE, GWP-ASan memberi Anda cakupan probabilistik dengan hampir tanpa overhead. Bersama-sama, mereka menjawab pertanyaan yang dulu memiliki jawaban yang menyedihkan.
Korupsi Memori adalah Bug yang Anda Kirim
Use-after-free, buffer overflow, dan heap corruption adalah bug yang lolos dari setiap test suite. Mereka hidup di kode native, sulit direproduksi, dan sering crash jauh dari kesalahan sebenarnya. Pada saat pengguna melaporkan crash, korupsi asli sudah terjadi, dan satu-satunya bukti Anda adalah stack trace yang hancur atau SIGSEGV di alamat yang terlihat acak.
Alat tradisional menangkap ini lebih awal. Valgrind akurat tetapi lambat. ASan lebih cepat tetapi masih menambahkan sekitar 2-3x overhead memori dan 2x overhead CPU. Itu baik-baik saja untuk fuzzing atau unit test. Itu tidak baik untuk perangkat bertenaga baterai yang menjalankan Instagram.
Produksi secara historis merupakan titik buta. Anda either mereproduksi bug secara lokal atau menebak-nebak.
Bagaimana ARM MTE Menandai Memori di Hardware
ARM Memory Tagging Extension, diperkenalkan di ARMv8.5-A, memberi CPU cara murah untuk memeriksa validitas pointer pada setiap akses memori. Caranya dengan menetapkan tag 4-bit ke setiap granula 16-byte memori dan menyimpan tag yang cocok di byte atas pointer.
Pada setiap load atau store, CPU membandingkan tag pointer dengan tag memori. Jika berbeda, hardware mengangkat fault. Ini bukan pemeriksaan perangkat lunak. Ini terjadi di subsistem memori, dan overheadnya biasanya di bawah 5%.
Byte atas pointer 64-bit sudah diabaikan oleh hardware. MTE menggunakan ulang bit-bit tersebut. Pointer seperti 0xb7f0_0000_1234_5678 membawa tag 0xb7. Memori di alamat tersebut harus membawa tag yang sama, atau aksesnya akan fault.
MTE tersedia di perangkat dengan ARMv8.5-A atau lebih baru. Itu termasuk Google Pixel 8 dan yang lebih baru, iPhone 15 Pro dan yang lebih baru, dan pangsa perangkat Android kelas menengah yang terus bertambah. Ini tidak universal, tetapi tidak lagi eksotis.
Kernel mengekspos MTE melalui flag prctl. Aplikasi dapat meminta pemeriksaan sinkron atau asinkron. Mode sinkron fault segera pada ketidakcocokan tag. Mode asinkron mengantre fault dan mengirimkannya nanti, yang lebih murah tetapi sedikit menunda deteksi.
GWP-ASan: Ketika Anda Tidak Dapat Menandai Semuanya
MTE hebat, tetapi memerlukan hardware yang kompatibel dan alokasi yang ditandai di seluruh codebase Anda. Jika Anda mengirim ke perangkat lama, atau jika Anda hanya ingin perlindungan yang ditargetkan, GWP-ASan adalah alternatif pragmatis.
GWP-ASan singkatan dari Guarded Write Protection AddressSanitizer. Ini adalah sampling allocator yang menempatkan sebagian kecil alokasi heap ke halaman yang dijaga yang dikelilingi oleh redzones yang diracuni. Jika use-after-free atau buffer overflow menyentuh redzones tersebut, MMU hardware memicu fault segera.
Wawasan utamanya adalah probabilitas. GWP-ASan mungkin menjaga 1 dari 10.000 alokasi. Itu terdengar tidak berguna sampai Anda menyadari bahwa aplikasi yang buggy akan merusak memori ribuan kali sebelum crash. Dengan sesi pengguna yang cukup, bahkan tingkat sampling 0,01% menangkap bug nyata di produksi.
Google telah menjalankan GWP-ASan di Chrome dan layanan sistem Android selama bertahun-tahun. Itu telah menemukan ratusan bug use-after-free yang lolos dari testing dan fuzzing. Overhead CPU dapat diabaikan karena hanya sebagian kecil alokasi yang melewati jalur yang dijaga. Overhead memori terbatas karena pool yang dijaga kecil dan alokasi pada akhirnya didaur ulang.
Pertukaran yang Tidak Ingin Dibicarakan Siapa Pun
MTE dan GWP-ASan tidak gratis. Mereka cukup murah untuk sepadan.
MTE memerlukan byte atas setiap pointer, yang berarti codebase Anda harus dikompilasi dengan -fsanitize=memtag atau -march=armv8.5-a+memtag. Jika Anda memiliki kode yang memmask pointer, menghashnya, atau melewatkannya melalui JNI tanpa penandaan yang tepat, Anda akan mendapatkan false positive. Android NDK dan implementasi libc modern menangani ini dengan benar, tetapi custom allocator atau skema packing pointer akan rusak.
MTE juga hanya menangkap bug di dalam region heap dan stack yang Anda tandai secara eksplisit. Pointer liar yang melompat ke region mmap yang tidak ditandai tidak akan tertangkap. Ini lebih baik daripada tidak sama sekali, tetapi bukan cakupan total.
GWP-ASan memiliki masalah yang berlawanan. Ini hanya menangkap alokasi yang kebetulan berada di pool yang dijaga. Bug yang merusak alokasi yang tidak dijaga akan tidak terdeteksi. Anda juga membayar biaya latensi kecil pada alokasi dan dealokasi untuk set yang di-sample, dan Anda perlu menangani crash dengan anggun di pipeline telemetry Anda.
Tidak ada alat yang menggantikan ASan di pipeline CI Anda. Mereka melengkapinya. ASan memberi Anda deteksi deterministik dengan cakupan tinggi selama pengujian. MTE dan GWP-ASan memberi Anda jaring pengaman di lapangan.
Mengaktifkan MTE di Perangkat Android Modern
Jika Anda memiliki Pixel 8 atau yang lebih baru, Anda dapat menguji MTE hari ini. Android NDK mendukungnya dengan satu flag compiler.
Pertama, periksa apakah perangkat Anda mendukung MTE:
#include <sys/prctl.h>
#include <linux/prctl.h>
#include <sys/auxv.h>
#include <asm/hwcap.h>
bool mte_supported() {
unsigned long hwcap2 = getauxval(AT_HWCAP2);
return (hwcap2 & HWCAP2_MTE) != 0;
}
Kemudian bangun library native Anda dengan MTE diaktifkan di CMakeLists.txt Anda:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memtag")
Jika Anda ingin faulting sinkron, yang biasanya Anda inginkan di produksi, mintalah saat startup:
#include <sys/prctl.h>
void enable_mte() {
if (mte_supported()) {
prctl(PR_SET_TAGGED_ADDR_CTRL,
PR_TAGGED_ADDR_ENABLE | PR_MTE_TCF_SYNC | (0xfffe << PR_MTE_TAG_SHIFT),
0, 0, 0);
}
}
Flag PR_MTE_TCF_SYNC memberitahu kernel untuk fault segera pada ketidakcocokan tag. Mask tag 0xfffe mengecualikan tag nol, yang masih digunakan oleh beberapa kode sistem.
Menyiapkan GWP-ASan untuk Produksi
GWP-ASan lebih mudah diaktifkan karena tidak memerlukan hardware khusus. Di Android, Anda dapat menautkan ke wrapper allocator GWP-ASan atau mengaktifkannya melalui allocator sistem pada level API yang lebih baru.
Untuk integrasi minimal, bungkus titik masuk allocator Anda:
extern "C" void* malloc(size_t size) {
if (__gwp_asan_sample()) {
return __gwp_asan_guarded_malloc(size);
}
return __libc_malloc(size);
}
Dalam praktiknya, Anda akan menggunakan runtime Android GWP-ASan atau implementasi LLVM compiler-rt. Kenop konfigurasi utama adalah tingkat sampling. Mulailah secara konservatif:
// Satu alokasi yang dijaga per 10.000
__gwp_asan_set_sample_rate(10000);
Kumpulkan crash yang dihasilkan melalui telemetry Anda yang ada. Crash GWP-ASan terlihat seperti SIGSEGV standar, tetapi alamat yang salah akan berada di halaman yang dijaga. Crash reporter Anda dapat mendeteksi ini dengan memeriksa apakah alamat yang salah berada dalam pool GWP-ASan.
Apa yang Sebenarnya Harus Anda Lakukan
Jika Anda mengirim kode native di mobile, Anda seharusnya sudah menjalankan ASan di CI dan fuzzing. Pertanyaannya adalah apa yang terjadi setelah Anda mengirim.
Jika perangkat minimum yang didukung Anda mencakup chip ARMv8.5-A, aktifkan MTE dalam mode sinkron. Overheadnya cukup rendah sehingga pengguna tidak akan menyadarinya, dan crash yang Anda dapatkan akan memiliki informasi ketidakcocokan tag yang akurat alih-alih korupsi heap yang acak.
Jika Anda mendukung perangkat lama, aktifkan GWP-ASan dengan tingkat sampling yang konservatif. Ini tidak akan menangkap setiap bug, tetapi akan menangkap bug yang tidak ditangkap oleh yang lain. Selama jutaan sesi, itu bukan teoretis. Begitulah cara Chrome menemukan use-after-free di image decoder-nya tahun lalu.
Hardware di ponsel Anda sudah mampu menangkap bug keamanan memori yang paling Anda takuti. Satu-satunya pertanyaan adalah apakah Anda telah mengaktifkannya.