ส่วนขยายการติดแท็กหน่วยความจำ Arm (MTE)

ทำไมต้องใช้ 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 ไบต์รายการเดียวด้วยค่า 0
  • STZ2G: ติดแท็กและเริ่มต้น Granule ขนาด 16 ไบต์ 2 รายการด้วยค่า 0
  • DC 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