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

ทำไมต้องใช้ MTE

ข้อบกพร่องด้านความปลอดภัยของหน่วยความจำ ซึ่งเป็นข้อผิดพลาดในการจัดการหน่วยความจำในภาษาการเขียนโปรแกรมแบบเนทีฟ เป็นปัญหาที่พบบ่อยในโค้ด ซึ่งนำไปสู่ช่องโหว่ด้านความปลอดภัยและ ปัญหาด้านความเสถียร

Armv9 ได้เปิดตัว Arm Memory Tagging Extension (MTE) ซึ่งเป็นส่วนขยายฮาร์ดแวร์ ที่ช่วยให้คุณตรวจพบข้อบกพร่องการใช้งานหลังช่วงใช้ฟรี (Use After Free) และบัฟเฟอร์ล้น (Buffer Overflow) ใน โค้ดแบบเนทีฟได้

ตรวจสอบการสนับสนุน

ตั้งแต่ Android 13 เป็นต้นไป อุปกรณ์บางรุ่นจะรองรับ MTE หากต้องการตรวจสอบว่าอุปกรณ์ของคุณใช้ MTE ที่เปิดใช้อยู่หรือไม่ ให้เรียกใช้คำสั่งต่อไปนี้ command:

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 (โคโมโด)
  • 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

นอกจากนี้ เมื่อเรียกใช้ในโหมด SYNC ตัวจัดสรร Android จะบันทึก สแต็กเทรซของการจัดสรรและการยกเลิกการจัดสรรทุกครั้ง และใช้ข้อมูลดังกล่าวเพื่อจัดทำรายงานข้อผิดพลาดที่ดีขึ้น ซึ่งรวมถึงคำอธิบายข้อผิดพลาดด้านหน่วยความจำ เช่น การใช้งานหลังช่วงใช้ฟรีหรือบัฟเฟอร์ล้น และสแต็กเทรซของเหตุการณ์หน่วยความจำที่เกี่ยวข้อง (ดูรายละเอียดเพิ่มเติมได้ที่ทำความเข้าใจรายงาน 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 แล้ว ให้ใช้และทดสอบแอปตามปกติ หากตรวจพบปัญหาด้านความปลอดภัยของหน่วยความจำ แอปจะขัดข้องพร้อมกับไฟล์ 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 ไบต์ เนื่องจากกำหนดแท็กได้เฉพาะ สำหรับก้อนขนาด 16 ไบต์ (หรือที่เรียกว่าแกรนูล)

จากนั้นก่อนที่จะส่งคืนพอยน์เตอร์ คุณต้องใช้คำสั่ง IRG เพื่อสร้างแท็กแบบสุ่มและจัดเก็บไว้ในพอยน์เตอร์

ทำตามวิธีการต่อไปนี้เพื่อติดแท็กหน่วยความจำพื้นฐาน

  • STG: ติดแท็กแกรนูลขนาด 16 ไบต์รายการเดียว
  • ST2G: ติดแท็กแกรนูล 16 ไบต์ 2 รายการ
  • DC GVA: ติดแท็กแคชไลน์ด้วยแท็กเดียวกัน

หรือคุณจะใช้คำสั่งต่อไปนี้เพื่อเริ่มต้นหน่วยความจำด้วยค่า 0 ก็ได้

  • STZG: ติดแท็กและเริ่มต้นด้วยค่า 0 สำหรับแกรนูลขนาด 16 ไบต์รายการเดียว
  • STZ2G: ติดแท็กและเริ่มต้นด้วย 0 สำหรับแกรนูล 2 รายการขนาด 16 ไบต์
  • DC GZVA: ติดแท็กและเริ่มต้นแคชไลน์ด้วยแท็กเดียวกัน

โปรดทราบว่า 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