เหตุผลที่ควรใช้ MTE
ข้อบกพร่องด้านความปลอดภัยของหน่วยความจำ ซึ่งเป็นข้อผิดพลาดในการจัดการหน่วยความจำในภาษาโปรแกรมแบบเนทีฟ เป็นปัญหาที่พบได้ทั่วไปเกี่ยวกับโค้ด ซึ่งจะทำให้เกิดช่องโหว่ด้านความปลอดภัยและปัญหาด้านความเสถียร
Armv9 ได้เปิดตัว Arm Memory Tagging Extension (MTE) ซึ่งเป็นส่วนขยายฮาร์ดแวร์ที่ช่วยให้คุณจับข้อบกพร่องการใช้งานหลังช่วงใช้ฟรี (Use After Free) และบัฟเฟอร์ล้น (Buffer Overflow) ในโค้ดเนทีฟได้
ตรวจสอบการสนับสนุน
ตั้งแต่ Android 13 เป็นต้นไป อุปกรณ์บางรุ่นจะรองรับ MTE หากต้องการตรวจสอบว่าอุปกรณ์ทํางานโดยเปิดใช้ MTE หรือไม่ ให้เรียกใช้คําสั่งต่อไปนี้
adb shell grep mte /proc/cpuinfo
หากผลลัพธ์คือ Features : [...] mte
แสดงว่าอุปกรณ์ของคุณทำงานโดยเปิดใช้ MTE
อุปกรณ์บางเครื่องไม่ได้เปิดใช้ MTE โดยค่าเริ่มต้น แต่อนุญาตให้นักพัฒนาแอปรีบูตโดยเปิดใช้ MTE นี่เป็นการกำหนดค่าเวอร์ชันทดลองที่ไม่แนะนำให้ใช้ในการทำงานปกติ เนื่องจากอาจทำให้ประสิทธิภาพหรือความเสถียรของอุปกรณ์ลดลง แต่อาจมีประโยชน์สำหรับการพัฒนาแอป หากต้องการเข้าถึงโหมดนี้ ให้ไปที่ตัวเลือกสำหรับนักพัฒนาแอป > ส่วนขยายการติดแท็กหน่วยความจำในแอปการตั้งค่า หากไม่มีตัวเลือกนี้ แสดงว่าอุปกรณ์ของคุณไม่รองรับการเปิดใช้ MTE ด้วยวิธีนี้
อุปกรณ์ที่รองรับ MTE
อุปกรณ์ต่อไปนี้รองรับ MTE
- Pixel 8 (Shiba)
- Pixel 8 Pro (Husky)
- Pixel 8a (Akita)
- Pixel 9 (Tokay)
- Pixel 9 Pro (Caiman)
- Pixel 9 Pro XL (Komodo)
- Pixel 9 Pro Fold (Comet)
- Pixel 9a (Tegu)
โหมดการทํางานของ MTE
MTE รองรับ 2 โหมด ได้แก่ SYNC และ ASYNC โหมด SYNC ให้ข้อมูลการวินิจฉัยที่ดีขึ้น จึงเหมาะสําหรับวัตถุประสงค์ด้านการพัฒนามากกว่า ส่วนโหมด ASYNC มีประสิทธิภาพสูงซึ่งช่วยให้เปิดใช้สําหรับแอปที่เผยแพร่แล้วได้
โหมดซิงค์ (SYNC)
โหมดนี้เพิ่มประสิทธิภาพสำหรับการแก้ไขข้อบกพร่องมากกว่าประสิทธิภาพ และสามารถใช้เป็นเครื่องมือตรวจหาข้อบกพร่องที่แม่นยำได้เมื่อยอมรับโอเวอร์เฮดด้านประสิทธิภาพที่สูงขึ้นได้ เมื่อเปิดใช้ MTE SYNC จะทำหน้าที่เป็นมาตรการบรรเทาปัญหาด้านความปลอดภัยด้วย
เมื่อแท็กไม่ตรงกัน โปรเซสเซอร์จะสิ้นสุดกระบวนการในคำสั่งโหลดหรือจัดเก็บที่ทำให้เกิดข้อผิดพลาดด้วย SIGSEGV (ที่มี si_code SEGV_MTESERR) และข้อมูลทั้งหมดเกี่ยวกับการเข้าถึงหน่วยความจำและที่อยู่ที่เกิดข้อผิดพลาด
โหมดนี้มีประโยชน์ในระหว่างการทดสอบเนื่องจากเป็นทางเลือกที่เร็วกว่า HWASan ที่ไม่จําเป็นต้องคอมไพล์โค้ดอีกครั้ง หรือในเวอร์ชันที่ใช้งานจริงเมื่อแอปของคุณแสดงถึงช่องโหว่ที่อาจถูกโจมตี นอกจากนี้ เมื่อโหมด ASYNC (อธิบายไว้ด้านล่าง) พบข้อบกพร่อง คุณจะได้รับรายงานข้อบกพร่องที่ถูกต้องโดยใช้ API รันไทม์เพื่อเปลี่ยนการดำเนินการเป็นโหมด SYNC
นอกจากนี้ เมื่อทํางานในโหมดซิงค์ เครื่องมือจัดสรรหน่วยความจําของ Android จะบันทึกสแต็กเทรซของการจัดสรรและการยกเลิกการจัดสรรทุกครั้ง และใช้ข้อมูลดังกล่าวเพื่อแสดงรายงานข้อผิดพลาดที่ดีขึ้นซึ่งมีคําอธิบายข้อผิดพลาดด้านหน่วยความจํา เช่น การใช้งานหลังช่วงใช้ฟรี (Use After Free) หรือบัฟเฟอร์ล้น (Buffer Overflow) และสแต็กเทรซของเหตุการณ์หน่วยความจําที่เกี่ยวข้อง (ดูรายละเอียดเพิ่มเติมที่ทําความเข้าใจรายงาน MTE) รายงานดังกล่าวจะให้ข้อมูลตามบริบทมากขึ้น และช่วยให้ติดตามและแก้ไขข้อบกพร่องได้ง่ายกว่าในโหมด ASYNC
โหมดอะซิงโครนัส (ASYNC)
โหมดนี้เพิ่มประสิทธิภาพเหนือความแม่นยำของรายงานข้อบกพร่อง และสามารถใช้เพื่อตรวจหาข้อบกพร่องด้านความปลอดภัยของหน่วยความจำที่มีโอเวอร์เฮดต่ำ เมื่อแท็กไม่ตรงกัน หน่วยประมวลผลจะดำเนินการต่อจนกว่าจะถึงรายการเคอร์เนลที่อยู่ใกล้ที่สุด (เช่น syscall หรือตัวขัดจังหวะตัวจับเวลา) ซึ่งจะสิ้นสุดกระบวนการด้วย SIGSEGV (โค้ด SEGV_MTEAERR) โดยไม่บันทึกที่อยู่หรือสิทธิ์เข้าถึงหน่วยความจำที่ผิดพลาด
โหมดนี้มีประโยชน์ในการลดช่องโหว่ด้านความปลอดภัยของหน่วยความจำในเวอร์ชันที่ใช้งานจริงบนโค้ดเบสที่ได้รับการทดสอบมาอย่างดี ซึ่งทราบกันดีว่าความหนาแน่นของข้อบกพร่องด้านความปลอดภัยของหน่วยความจำอยู่ในระดับต่ำ ซึ่งทำได้โดยใช้โหมด SYNC ระหว่างการทดสอบ
เปิดใช้ MTE
สำหรับอุปกรณ์เครื่องเดียว
สำหรับการทดสอบ คุณสามารถใช้การเปลี่ยนแปลงความเข้ากันได้ของแอปเพื่อตั้งค่าเริ่มต้นของค่าแอตทริบิวต์ memtagMode
สําหรับแอปพลิเคชันที่ไม่ได้ระบุค่าใดๆ ในไฟล์ Manifest (หรือระบุ "default"
)
ซึ่งดูได้ที่ระบบ > ขั้นสูง > ตัวเลือกสำหรับนักพัฒนาแอป > การเปลี่ยนแปลงความเข้ากันได้ของแอปในเมนูการตั้งค่าส่วนกลาง การตั้งค่า NATIVE_MEMTAG_ASYNC
หรือ NATIVE_MEMTAG_SYNC
จะเปิดใช้ MTE สําหรับแอปพลิเคชันหนึ่งๆ
หรือจะตั้งค่าโดยใช้คำสั่ง am
ก็ได้ ดังนี้
- สำหรับโหมด SYNC
$ adb shell am compat enable NATIVE_MEMTAG_SYNC my.app.name
- สำหรับโหมด ASYNC
$ adb shell am compat enable NATIVE_MEMTAG_ASYNC my.app.name
ใน Gradle
คุณสามารถเปิดใช้ MTE สำหรับบิลด์แก้ไขข้อบกพร่องทั้งหมดของโปรเจ็กต์ Gradle ได้โดยใส่
<?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>
เข้าสู่ app/src/debug/AndroidManifest.xml
ซึ่งจะลบล้าง memtagMode
ของไฟล์ Manifest โดยใช้การซิงค์สำหรับบิลด์แก้ไขข้อบกพร่อง
หรือจะเปิดใช้ MTE สำหรับบิลด์ทั้งหมดของ buildType ที่กําหนดเองก็ได้ โดยสร้าง buildType ของคุณเอง แล้วใส่ XML ลงใน app/src/<name of buildType>/AndroidManifest.xml
สำหรับ APK ในอุปกรณ์ที่พร้อมใช้งาน
MTE จะปิดอยู่โดยค่าเริ่มต้น แอปที่ต้องการใช้ MTE สามารถทำได้โดยการตั้งค่า android:memtagMode
ภายใต้แท็ก <application>
หรือ <process>
ใน AndroidManifest.xml
android:memtagMode=(off|default|sync|async)
เมื่อตั้งค่าในแท็ก <application>
แอตทริบิวต์จะส่งผลต่อกระบวนการทั้งหมดที่แอปพลิเคชันใช้ และสามารถลบล้างสำหรับแต่ละกระบวนการได้โดยการตั้งค่าแท็ก <process>
บิลด์ด้วยเครื่องมือวัด
การเปิดใช้ MTE ตามที่อธิบายไว้ก่อนหน้านี้จะช่วยตรวจหาข้อบกพร่องความเสียหายของหน่วยความจำในกองหน่วยความจำหลัก หากต้องการตรวจหาความเสียหายของหน่วยความจำบนสแต็ก นอกเหนือจากการเปิดใช้ MTE สําหรับแอปแล้ว จะต้องสร้างโค้ดขึ้นมาใหม่โดยใช้เครื่องมือวัด แอปที่ได้จะทำงานในอุปกรณ์ที่รองรับ MTE เท่านั้น
หากต้องการสร้างโค้ดเนทีฟ (JNI) ของแอปด้วย MTE ให้ทําดังนี้
ndk-build
ในไฟล์ Application.mk
APP_CFLAGS := -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag
APP_LDFLAGS := -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag
CMake
สําหรับแต่ละเป้าหมายใน CMakeLists.txt ให้ทำดังนี้
target_compile_options(${TARGET} PUBLIC -fsanitize=memtag -fno-omit-frame-pointer -march=armv8-a+memtag)
target_link_options(${TARGET} PUBLIC -fsanitize=memtag -fsanitize-memtag-mode=sync -march=armv8-a+memtag)
เรียกใช้แอป
เมื่อเปิดใช้ MTE แล้ว ให้ใช้และทดสอบแอปตามปกติ หากตรวจพบปัญหาด้านความปลอดภัยของหน่วยความจำ แอปจะขัดข้องพร้อมแสดงรายการที่ตายซึ่งมีลักษณะคล้ายกับนี้ (โปรดสังเกต SIGSEGV
ที่มี SEGV_MTESERR
สำหรับ SYNC หรือ SEGV_MTEAERR
สำหรับ 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
ดูรายละเอียดเพิ่มเติมได้ที่การทำความเข้าใจรายงาน MTE ในเอกสารประกอบ AOSP นอกจากนี้ คุณยังแก้ไขข้อบกพร่องของแอปด้วย Android Studio ได้ด้วย โดยโปรแกรมแก้ไขข้อบกพร่องจะหยุดอยู่ที่บรรทัดที่ทำให้การเข้าถึงหน่วยความจำไม่ถูกต้อง
ผู้ใช้ขั้นสูง: การใช้ MTE ในตัวจัดสรรของคุณเอง
หากต้องการใช้ MTE สําหรับหน่วยความจําที่ไม่ได้จัดสรรผ่านตัวจัดสรรหน่วยความจําของระบบตามปกติ คุณจะต้องแก้ไขตัวจัดสรรหน่วยความจําเพื่อติดแท็กหน่วยความจําและพอยน์เตอร์
หน้าเว็บสำหรับผู้จัดสรรต้องได้รับการจัดสรรโดยใช้ PROT_MTE
ใน Flag prot
ของ mmap
(หรือ mprotect
)
การจัดสรรที่ติดแท็กทั้งหมดต้องสอดคล้องกับ 16 ไบต์ เนื่องจากสามารถกำหนดแท็กให้กับข้อมูลขนาด 16 ไบต์ (หรือที่เรียกว่า Granule) เท่านั้น
จากนั้นก่อนแสดงผลพอยน์เตอร์ คุณต้องใช้คำสั่ง IRG
เพื่อสร้างแท็กแบบสุ่มและจัดเก็บไว้ในพอยน์เตอร์
ใช้วิธีการต่อไปนี้เพื่อติดแท็กหน่วยความจําพื้นฐาน
STG
: ติดแท็กเกรนขนาด 16 ไบต์รายการเดียวST2G
: ติดแท็กเม็ดข้อมูล 16 ไบต์ 2 รายการDC GVA
: บรรทัดแคชแท็กที่มีแท็กเดียวกัน
หรือคุณจะใช้วิธีการต่อไปนี้เพื่อเริ่มต้นหน่วยความจำเป็น 0 ก็ได้
STZG
: ติดแท็กและเริ่มต้นค่าเป็น 0 สำหรับ Granule ขนาด 16 ไบต์รายการเดียวSTZ2G
: ติดแท็กและเริ่มต้นค่าเป็น 0 สำหรับ Granule ขนาด 16 ไบต์ 2 รายการDC GZVA
: ติดแท็กและเริ่มต้นค่าแคชไลน์เป็น 0 ด้วยแท็กเดียวกัน
โปรดทราบว่า CPU รุ่นเก่าไม่รองรับชุดคำสั่งเหล่านี้ คุณจึงต้องเรียกใช้ชุดคำสั่งเหล่านี้แบบมีเงื่อนไขเมื่อเปิดใช้ MTE คุณตรวจสอบได้ว่า MTE เปิดใช้สำหรับกระบวนการของคุณหรือไม่ โดยทำดังนี้
#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;
}
การติดตั้งใช้งาน Scudo อาจเป็นข้อมูลอ้างอิงที่ดี
ดูข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมได้ที่คู่มือผู้ใช้ MTE สําหรับระบบปฏิบัติการ Android ที่เขียนโดย Arm