ทำไมต้องใช้ MTE
ข้อบกพร่องด้านความปลอดภัยของหน่วยความจำ ซึ่งเป็นข้อผิดพลาดในการจัดการหน่วยความจำในภาษาโปรแกรมแบบเนทีฟ เป็นปัญหาที่พบบ่อยในโค้ด ข้อบกพร่องเหล่านี้ทำให้เกิดช่องโหว่ด้านความปลอดภัยและปัญหาด้านความเสถียร
Armv9 ได้เปิดตัว Arm Memory Tagging Extension (MTE) ซึ่งเป็นส่วนขยายฮาร์ดแวร์ที่ช่วยให้คุณตรวจพบข้อบกพร่องการใช้งานหลังช่วงใช้ฟรี (Use After Free) และบัฟเฟอร์ล้น (Buffer Overflow) ในโค้ดแบบเนทีฟ
ตรวจสอบการรองรับ
อุปกรณ์บางรุ่นรองรับ MTE ตั้งแต่ Android 13 เป็นต้นไป หากต้องการตรวจสอบว่าอุปกรณ์ของคุณทำงานโดยเปิดใช้ MTE หรือไม่ ให้เรียกใช้คำสั่งต่อไปนี้
adb shell grep mte /proc/cpuinfo
หากผลลัพธ์เป็น Features : [...] mte แสดงว่าอุปกรณ์ของคุณทำงานโดยเปิดใช้ MTE
อุปกรณ์บางรุ่นไม่ได้เปิดใช้ MTE โดยค่าเริ่มต้น แต่จะอนุญาตให้นักพัฒนาแอปรีบูตโดยเปิดใช้ MTE นี่เป็นการกำหนดค่าเชิงทดลองที่ไม่แนะนำให้ใช้ตามปกติ เนื่องจากอาจลดประสิทธิภาพหรือความเสถียรของอุปกรณ์ แต่ก็อาจมีประโยชน์สำหรับการพัฒนาแอป หากต้องการเข้าถึงโหมดนี้ ให้ไปที่ ตัวเลือกสำหรับนักพัฒนาแอป > Memory Tagging Extension ในแอปการตั้งค่า หากไม่มี ตัวเลือกนี้ แสดงว่าอุปกรณ์ของคุณไม่รองรับการเปิดใช้ 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)
- Pixel 10 (Frankel)
- Pixel 10 Pro (Blazer)
- Pixel 10 Pro XL (Mustang)
- Pixel 10 Pro Fold (Rango)
- Pixel 10a (Stallion)
โหมดการทำงานของ MTE
MTE รองรับ 2 โหมด ได้แก่ SYNC และ ASYNC โหมด SYNC ให้ข้อมูลการวินิจฉัยที่ดีกว่า จึงเหมาะสำหรับวัตถุประสงค์ในการพัฒนา ในขณะที่โหมด ASYNC มีประสิทธิภาพสูง จึงเปิดใช้กับแอปที่เผยแพร่แล้วได้
โหมดซิงโครนัส (SYNC)
โหมดนี้ได้รับการปรับให้เหมาะกับการแก้ไขข้อบกพร่องมากกว่าประสิทธิภาพ และใช้เป็นเครื่องมือตรวจจับข้อบกพร่องที่แม่นยำได้เมื่อยอมรับค่าใช้จ่ายด้านประสิทธิภาพที่สูงขึ้นได้ เมื่อเปิดใช้ MTE SYNC จะทำหน้าที่เป็นการลดปัญหาด้านความปลอดภัยด้วย
เมื่อแท็กไม่ตรงกัน โปรเซสเซอร์จะยุติกระบวนการในคำสั่งโหลดหรือจัดเก็บที่ทำให้เกิดปัญหาด้วย SIGSEGV (โดยมี si_code SEGV_MTESERR) และข้อมูลทั้งหมดเกี่ยวกับการเข้าถึงหน่วยความจำและที่อยู่ที่มีข้อผิดพลาด
โหมดนี้มีประโยชน์ระหว่างการทดสอบเนื่องจากเป็นทางเลือกที่เร็วกว่า HWASan ซึ่ง ไม่จำเป็นต้องคอมไพล์โค้ดใหม่ หรือในสภาพแวดล้อมจริงเมื่อแอปของคุณ แสดงถึงพื้นผิวการโจมตีที่เสี่ยง นอกจากนี้ เมื่อโหมด ASYNC (อธิบายไว้ด้านล่าง) พบข้อบกพร่อง คุณจะได้รับรายงานข้อบกพร่องที่ถูกต้องโดยใช้ API รันไทม์เพื่อเปลี่ยนการดำเนินการเป็นโหมด SYNC
นอกจากนี้ เมื่อทำงานในโหมด SYNC ตัวจัดสรรของ Android จะบันทึก Stack Trace ของการจัดสรรและการยกเลิกการจัดสรรทุกครั้ง และใช้ข้อมูลดังกล่าวเพื่อสร้าง รายงานข้อผิดพลาดที่ดีขึ้น ซึ่งรวมถึงคำอธิบายข้อผิดพลาดของหน่วยความจำ เช่น การใช้งานหลังช่วงใช้ฟรี (Use After Free) หรือบัฟเฟอร์ล้น (Buffer Overflow) และ Stack Trace ของเหตุการณ์หน่วยความจำที่เกี่ยวข้อง (ดูรายละเอียดเพิ่มเติมได้ที่ ทำความเข้าใจรายงาน 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 ตามที่อธิบายไว้ก่อนหน้านี้จะช่วยตรวจจับข้อบกพร่องการเสียหายของหน่วยความจำในฮีปแบบเนทีฟ หากต้องการตรวจจับการเสียหายของหน่วยความจำใน Stack นอกเหนือจากการเปิดใช้ 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 แล้ว ให้ใช้และทดสอบแอปตามปกติ หากตรวจพบปัญหาด้านความปลอดภัยของหน่วยความจำ แอปจะขัดข้องโดยมี Tombstone ที่มีลักษณะคล้ายกับตัวอย่างต่อไปนี้ (โปรดสังเกต 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 ในแฟล็ก
prot ของ mmap (หรือ mprotect)
การจัดสรรที่ติดแท็กทั้งหมดต้องมีการจัดแนว 16 ไบต์ เนื่องจากระบบจะกำหนดแท็กได้เฉพาะกับ Chunk ขนาด 16 ไบต์ (หรือที่เรียกว่า Granule)
จากนั้นก่อนที่จะส่งคืนพอยน์เตอร์ คุณต้องใช้คำสั่ง IRG เพื่อ
สร้างแท็กแบบสุ่มและจัดเก็บไว้ในพอยน์เตอร์
ใช้คำแนะนำต่อไปนี้เพื่อติดแท็กหน่วยความจำพื้นฐาน
STG: ติดแท็ก Granule ขนาด 16 ไบต์รายการเดียวST2G: ติดแท็ก Granule ขนาด 16 ไบต์ 2 รายการDC GVA: ติดแท็ก Cacheline ด้วยแท็กเดียวกัน
หรือคำแนะนำต่อไปนี้จะเริ่มต้นหน่วยความจำด้วยค่า 0 ด้วย
STZG: ติดแท็กและเริ่มต้น Granule ขนาด 16 ไบต์รายการเดียวด้วยค่า 0STZ2G: ติดแท็กและเริ่มต้น Granule ขนาด 16 ไบต์ 2 รายการด้วยค่า 0DC GZVA: ติดแท็กและเริ่มต้น Cacheline ด้วยแท็กเดียวกันด้วยค่า 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