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

เหตุผลที่ควรใช้ 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