Arm Memory Tagging Extension (MTE)

Mengapa MTE?

Bug keamanan memori, yang merupakan error dalam menangani memori dalam bahasa pemrograman native, adalah masalah kode yang umum. Hal ini menyebabkan kerentanan keamanan serta masalah stabilitas.

Armv9 memperkenalkan Arm Memory Tagging Extension (MTE), ekstensi hardware yang memungkinkan Anda menangkap bug use-after-free dan buffer-overflow dalam kode native.

Memeriksa dukungan

Mulai dari Android 13, perangkat tertentu memiliki dukungan untuk MTE. Untuk memeriksa apakah perangkat Anda berjalan dengan MTE yang diaktifkan, jalankan perintah berikut:

adb shell grep mte /proc/cpuinfo

Jika hasilnya adalah Features : [...] mte, berarti perangkat Anda berjalan dengan MTE yang diaktifkan.

Beberapa perangkat tidak mengaktifkan MTE secara default, tetapi mengizinkan developer memulai ulang dengan MTE yang diaktifkan. Ini adalah konfigurasi eksperimental yang tidak direkomendasikan untuk penggunaan normal karena dapat menurunkan performa atau stabilitas perangkat, tetapi dapat berguna untuk pengembangan aplikasi. Untuk mengakses mode ini, buka Opsi Developer > Memory Tagging Extension di Aplikasi Setelan. Jika opsi ini tidak ada, berarti perangkat tidak mendukung pengaktifan MTE dengan cara ini.

Mode operasi MTE

MTE mendukung dua mode: SYNC dan ASYNC. Mode SYNC memberikan informasi diagnostik yang lebih baik sehingga lebih cocok untuk tujuan pengembangan, sedangkan mode ASYNC memiliki performa tinggi yang memungkinkannya diaktifkan untuk aplikasi yang dirilis.

Mode sinkron (SYNC)

Mode ini dioptimalkan untuk kemampuan debug atas performa dan dapat digunakan sebagai alat deteksi bug yang presisi, saat overhead performa yang lebih tinggi dapat diterima. Jika diaktifkan, MTE SYNC juga berfungsi sebagai mitigasi keamanan.

Jika ada ketidakcocokan tag, prosesor akan menghentikan proses pada pemuatan yang melakukan pelanggaran atau menyimpan petunjuk dengan SIGSEGV (dengan si_code SEGV_MTESERR) dan informasi lengkap tentang akses memori dan alamat faulting.

Mode ini berguna selama pengujian sebagai alternatif yang lebih cepat untuk HWASan yang tidak mengharuskan Anda mengompilasi ulang kode, atau dalam produksi, saat aplikasi Anda merepresentasikan kerentanan platform terhadap serangan. Selain itu, jika mode ASYNC (dijelaskan di bawah) telah menemukan bug, laporan bug yang akurat dapat diperoleh dengan menggunakan API runtime untuk mengalihkan eksekusi ke mode SYNC.

Selain itu, saat berjalan dalam mode SYNC, pengalokasi Android akan merekam stack trace setiap alokasi dan dealokasi serta menggunakannya untuk memberikan laporan error yang lebih baik yang menyertakan penjelasan tentang error memori, seperti use-after-free atau buffer-overflow, dan stack trace peristiwa memori yang relevan (lihat Memahami laporan MTE untuk mengetahui detail selengkapnya). Laporan tersebut memberikan informasi yang lebih kontekstual dan membuat bug lebih mudah dilacak dan diperbaiki daripada dalam mode ASYNC.

Mode asinkron (ASYNC)

Mode ini dioptimalkan untuk performa atas akurasi laporan bug dan dapat digunakan untuk mendeteksi overhead rendah dari bug keamanan memori. Jika ada ketidakcocokan tag, pemroses akan melanjutkan eksekusi hingga entri kernel terdekat (seperti syscall atau interupsi timer), yang menghentikan proses dengan SIGSEGV (kode SEGV_MTEAERR) tanpa merekam alamat faulting atau akses memori.

Mode ini berguna untuk memitigasi kerentanan keamanan memori dalam produksi pada codebase yang telah diuji dengan baik, dengan kepadatan bug keamanan memori yang diketahui rendah, yang dicapai dengan menggunakan mode SYNC selama pengujian.

Mengaktifkan MTE

Untuk satu perangkat

Untuk eksperimen, perubahan kompatibilitas aplikasi dapat digunakan untuk menetapkan nilai default atribut memtagMode untuk aplikasi yang tidak menentukan nilai apa pun dalam manifes (atau menentukan "default").

Fitur ini dapat ditemukan di bagian Sistem > Lanjutan > Opsi developer > Perubahan Kompatibilitas Aplikasi di menu setelan global. Menetapkan NATIVE_MEMTAG_ASYNC atau NATIVE_MEMTAG_SYNC akan mengaktifkan MTE untuk aplikasi tertentu.

Anda juga dapat menyetelnya menggunakan perintah am sebagai berikut:

  • Untuk mode SYNC: $ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
  • Untuk mode ASYNC: $ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name

Di Gradle

Anda dapat mengaktifkan MTE untuk semua build debug project Gradle dengan menempatkan

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application android:memtagMode="sync" tools:replace="android:memtagMode"/>
</manifest>

menjadi app/src/debug/AndroidManifest.xml. Tindakan ini akan mengganti memtagMode manifes Anda dengan sinkronisasi untuk build debug.

Atau, Anda dapat mengaktifkan MTE untuk semua build buildType kustom. Untuk melakukannya, buat buildType Anda sendiri dan masukkan XML ke dalam app/src/<name of buildType>/AndroidManifest.xml.

Untuk APK di perangkat apa pun yang mendukung

MTE dinonaktifkan secara default. Aplikasi yang ingin menggunakan MTE dapat melakukannya dengan menyetel android:memtagMode pada tag <application> atau <process> di AndroidManifest.xml.

android:memtagMode=(off|default|sync|async)

Jika ditetapkan pada tag <application>, atribut tersebut akan memengaruhi semua proses yang digunakan oleh aplikasi, dan dapat diganti untuk setiap proses dengan menetapkan tag <process>.

Menjalankan aplikasi

Setelah mengaktifkan MTE, gunakan dan uji aplikasi Anda seperti biasa. Jika masalah keamanan memori terdeteksi, aplikasi Anda akan mengalami error dengan tombstone yang terlihat seperti ini (perhatikan SIGSEGV dengan SEGV_MTESERR untuk SYNC atau SEGV_MTEAERR untuk ASYNC):

pid: 13935, tid: 13935, name: sanitizer-statu  >>> sanitizer-status <<<
uid: 0
tagged_addr_ctrl: 000000000007fff3
signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x800007ae92853a0
Cause: [MTE]: Use After Free, 0 bytes into a 32-byte allocation at 0x7ae92853a0
x0  0000007cd94227cc  x1  0000007cd94227cc  x2  ffffffffffffffd0  x3  0000007fe81919c0
x4  0000007fe8191a10  x5  0000000000000004  x6  0000005400000051  x7  0000008700000021
x8  0800007ae92853a0  x9  0000000000000000  x10 0000007ae9285000  x11 0000000000000030
x12 000000000000000d  x13 0000007cd941c858  x14 0000000000000054  x15 0000000000000000
x16 0000007cd940c0c8  x17 0000007cd93a1030  x18 0000007cdcac6000  x19 0000007fe8191c78
x20 0000005800eee5c4  x21 0000007fe8191c90  x22 0000000000000002  x23 0000000000000000
x24 0000000000000000  x25 0000000000000000  x26 0000000000000000  x27 0000000000000000
x28 0000000000000000  x29 0000007fe8191b70
lr  0000005800eee0bc  sp  0000007fe8191b60  pc  0000005800eee0c0  pst 0000000060001000

backtrace:
      #00 pc 00000000000010c0  /system/bin/sanitizer-status (test_crash_malloc_uaf()+40) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #01 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #02 pc 00000000000019cc  /system/bin/sanitizer-status (main+1032) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000487d8  /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+96) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)

deallocated by thread 13935:
      #00 pc 000000000004643c  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::quarantineOrDeallocateChunk(scudo::Options, void*, scudo::Chunk::UnpackedHeader*, unsigned long)+688) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 00000000000421e4  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::deallocate(void*, scudo::Chunk::Origin, unsigned long, unsigned long)+212) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 00000000000010b8  /system/bin/sanitizer-status (test_crash_malloc_uaf()+32) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #03 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)

allocated by thread 13935:
      #00 pc 0000000000042020  /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::allocate(unsigned long, scudo::Chunk::Origin, unsigned long, bool)+1300) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #01 pc 0000000000042394  /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #02 pc 000000000003cc9c  /apex/com.android.runtime/lib64/bionic/libc.so (malloc+36) (BuildId: 6ab39e35a2fae7efbe9a04e9bbb14331)
      #03 pc 00000000000010ac  /system/bin/sanitizer-status (test_crash_malloc_uaf()+20) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
      #04 pc 00000000000014a4  /system/bin/sanitizer-status (test(void (*)())+132) (BuildId: 953fc93301472d0b72709b2b9a9f6f30)
Learn more about MTE reports: https://source.android.com/docs/security/test/memory-safety/mte-report

Lihat Memahami laporan MTE dalam dokumentasi AOSP untuk mengetahui detail selengkapnya. Anda juga dapat men-debug aplikasi dengan Android Studio dan debugger berhenti pada baris yang menyebabkan akses memori tidak valid.

Pengguna Lanjutan: Menggunakan MTE di alokator Anda sendiri

Agar dapat menggunakan MTE untuk memori yang tidak dialokasikan melalui alokator sistem normal, Anda harus mengubah alokator untuk memberi tag pada memori dan pointer.

Halaman untuk pengalokasi Anda harus dialokasikan menggunakan PROT_MTE dalam tanda prot dari mmap (atau mprotect).

Semua alokasi yang diberi tag harus diselaraskan dengan 16 byte, karena tag hanya dapat ditetapkan untuk potongan 16 byte (juga dikenal sebagai granule).

Kemudian, sebelum menampilkan pointer, Anda perlu menggunakan petunjuk IRG untuk membuat tag acak dan menyimpannya di pointer.

Gunakan petunjuk berikut untuk memberi tag pada memori yang mendasari:

  • STG: memberi tag pada satu granule 16 byte
  • ST2G: memberi tag pada dua granule 16 byte
  • DC GVA: cacheline tag dengan tag yang sama

Atau, petunjuk berikut juga melakukan inisialisasi nol pada memori:

  • STZG: memberi tag dan melakukan inisialisasi nol pada satu granule 16 byte
  • STZ2G: memberi tag dan melakukan inisialisasi nol pada dua granule 16 byte
  • DC GZVA: memberi tag dan melakukan inisialisasi nol pada cacheline dengan tag yang sama

Perhatikan bahwa petunjuk ini tidak didukung di CPU lama, sehingga Anda perlu menjalankannya secara bersyarat saat MTE diaktifkan. Anda dapat memeriksa apakah MTE diaktifkan untuk proses Anda:

#include <sys/prctl.h>

bool runningWithMte() {
      int mode = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
      return mode != -1 && mode & PR_MTE_TCF_MASK;
}

Penerapan scudo mungkin akan bermanfaat sebagai referensi.

Pelajari lebih lanjut

Anda dapat mempelajari lebih lanjut di Panduan Pengguna MTE untuk Android OS yang ditulis oleh Arm.