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

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

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

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

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

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

adb shell grep mte /proc/cpuinfo

หากผลลัพธ์คือ Features : [...] mte แสดงว่าอุปกรณ์กำลังทำงานโดยใช้ MTE เปิดอยู่

อุปกรณ์บางเครื่องไม่เปิดใช้ MTE โดยค่าเริ่มต้น แต่อนุญาตให้นักพัฒนาแอปรีบูตด้วย เปิดใช้ MTE อยู่ นี่เป็นการกำหนดค่าแบบทดลองที่เราไม่แนะนำสำหรับ การใช้งานตามปกติ เพราะอาจทำให้ประสิทธิภาพหรือความเสถียรของอุปกรณ์ลดลง แต่ ซึ่งมีประโยชน์สำหรับการพัฒนาแอป หากต้องการเข้าถึงโหมดนี้ ให้ไปที่ ตัวเลือกสำหรับนักพัฒนาซอฟต์แวร์ > ส่วนขยายการติดแท็กหน่วยความจำในแอปการตั้งค่า หากสิ่งนี้ ไม่มีตัวเลือกดังกล่าว แสดงว่าอุปกรณ์ของคุณไม่รองรับการเปิดใช้ MTE ด้วยวิธีนี้

โหมดการทํางานของ MTE

MTE รองรับ 2 โหมด ได้แก่ ซิงค์และ ASYNC โหมดซิงค์ช่วยให้วินิจฉัยได้ดียิ่งขึ้น ข้อมูลนี้เหมาะสมกว่าสำหรับวัตถุประสงค์ด้านการพัฒนา ขณะที่โหมด ASYNC มีประสิทธิภาพสูง ทำให้สามารถเปิดใช้สำหรับแอปที่เผยแพร่แล้วได้

โหมดซิงโครนัส (SYNC)

โหมดนี้ได้รับการเพิ่มประสิทธิภาพเพื่อให้แก้ไขข้อบกพร่องได้ในส่วนต่างๆ ของประสิทธิภาพ ใช้เป็นเครื่องมือตรวจหาข้อบกพร่องที่แม่นยำได้ เมื่อค่าใช้จ่ายในการดำเนินการที่สูงขึ้น ที่ยอมรับได้ เมื่อเปิดใช้ MTE SYNC ไว้จะช่วยบรรเทาความปลอดภัยได้เช่นกัน

เมื่อแท็กไม่ตรงกัน ตัวประมวลผลจะสิ้นสุดกระบวนการในการโหลดที่ไม่เหมาะสมหรือ จัดเก็บคำสั่งด้วย SIGSEGV (พร้อม si_code SEGV_MTESERR) และข้อมูลทั้งหมด เกี่ยวกับการเข้าถึงหน่วยความจำและที่อยู่ที่เสียหาย

โหมดนี้มีประโยชน์ในระหว่างการทดสอบ เพราะเป็นทางเลือกที่เร็วกว่า HWASan ซึ่ง ไม่ต้องคอมไพล์โค้ดอีกครั้งหรือในเวอร์ชันที่ใช้งานจริงเมื่อแอป แสดงถึงพื้นที่การโจมตีที่มีช่องโหว่ นอกจากนี้ เมื่อโหมด ASYNC (อธิบายไว้ด้านล่าง) พบว่ามี คุณสามารถรับรายงานข้อบกพร่องที่แม่นยำได้โดยใช้ API รันไทม์เพื่อเปลี่ยน การดำเนินการกับโหมด SYNC

นอกจากนี้ เมื่อทำงานในโหมดซิงค์ ตัวจัดสรร Android จะบันทึก สแต็กเทรซของการจัดสรรและการสร้างดีลทั้งหมด แล้วใช้เพื่อให้ รายงานข้อผิดพลาดที่มีคำอธิบายเกี่ยวกับหน่วยความจำผิดพลาด เช่น ใช้หลังช่วงฟรีหรือบัฟเฟอร์ล้นเกิน และสแต็กเทรซของหน่วยความจำที่เกี่ยวข้อง เหตุการณ์ (ดูรายละเอียดเพิ่มเติมในการทำความเข้าใจรายงาน MTE) เช่น ให้ข้อมูลตามบริบทมากขึ้น และช่วยให้ติดตามข้อบกพร่องได้ง่ายขึ้น มากกว่าในโหมด ASYNC

โหมดอะซิงโครนัส (ASYNC)

โหมดนี้ได้รับการเพิ่มประสิทธิภาพเพื่อให้มีประสิทธิภาพมากกว่าความแม่นยำของรายงานข้อบกพร่อง ซึ่งใช้ในการตรวจจับข้อบกพร่องด้านความปลอดภัยของหน่วยความจำแบบมีค่าใช้จ่ายต่ำ เมื่อแท็กไม่ตรงกัน ค่า ตัวประมวลผลจะยังคงดำเนินการต่อไปจนกว่าจะพบรายการเคอร์เนลที่ใกล้ที่สุด (เช่น syscall หรือตัวจับเวลาถอยหลัง) ซึ่งจะทำให้กระบวนการหยุดลงด้วย SIGSEGV (โค้ด SEGV_MTEAERR) โดยไม่บันทึกที่อยู่หรือการเข้าถึงหน่วยความจำที่ผิดพลาด

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

เปิดใช้ MTE

สำหรับอุปกรณ์เครื่องเดียว

สำหรับการทดสอบ คุณสามารถใช้การเปลี่ยนแปลงความเข้ากันได้ของแอปเพื่อตั้งค่าเริ่มต้นได้ ค่าของแอตทริบิวต์ memtagMode สำหรับแอปพลิเคชันที่ไม่ได้ระบุ ค่าใดก็ได้ในไฟล์ Manifest (หรือระบุ "default")

กฎเหล่านี้จะอยู่ในส่วน "ระบบ" > ขั้นสูง > ตัวเลือกของนักพัฒนาซอฟต์แวร์ > แอป การเปลี่ยนแปลงความเข้ากันได้ในเมนูการตั้งค่าส่วนกลาง การตั้งค่า NATIVE_MEMTAG_ASYNC หรือ NATIVE_MEMTAG_SYNC จะเปิดใช้ MTE สำหรับแอปพลิเคชันหนึ่งๆ

หรือจะตั้งค่าได้โดยใช้คำสั่ง am ดังนี้

  • สำหรับโหมดซิงค์: $ 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 การดำเนินการนี้จะแทนที่ไฟล์ Manifest memtagMode พร้อมการซิงค์สำหรับบิลด์การแก้ไขข้อบกพร่อง

คุณสามารถเปิดใช้ 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

ผู้ผลิต

สำหรับแต่ละเป้าหมายใน 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 Flag ของ mmap (หรือ mprotect)

การจัดสรรที่ติดแท็กทั้งหมดจะต้องสอดคล้องกัน 16 ไบต์ เนื่องจากจะกำหนดแท็กได้เพียงอย่างเดียว ขนาด 16 ไบต์ (หรือเรียกว่าเม็ด)

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

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

  • STG: ติดแท็กเม็ดเดียวขนาด 16 ไบต์
  • ST2G: แท็กเม็ดขนาด 16 ไบต์ 2 เม็ด
  • DC GVA: แท็กไลน์สำหรับแท็กที่มีแท็กเดียวกัน

นอกจากนี้ คำแนะนำต่อไปนี้ยังกำหนดค่าหน่วยความจำให้เป็นศูนย์:

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