ทำความเข้าใจรายงาน MTE

SIGSEGV ล้มเหลวด้วยรหัส 9 (SEGV_MTESERR) หรือรหัส 8 (SEGV_MTEAERR) เป็นข้อบกพร่องในการแท็กหน่วยความจำ Memory Tagging Extension (MTE) เป็นฟีเจอร์ Armv9 ที่รองรับใน Android 12 ขึ้นไป MTE คือการใช้งานฮาร์ดแวร์ของหน่วยความจำที่ติดแท็ก โดยให้การป้องกันหน่วยความจำแบบละเอียดสำหรับการตรวจจับและบรรเทา ข้อบกพร่องด้านความปลอดภัยของหน่วยความจำ

ใน C/C++ ตัวชี้ที่ส่งคืนจากการเรียกไปยัง malloc() หรือตัวดำเนินการ new() หรือฟังก์ชันที่คล้ายกันสามารถใช้เพื่อเข้าถึงหน่วยความจำภายในขอบเขตของการจัดสรรนั้น และเฉพาะในขณะที่การจัดสรรยังคงอยู่ (ไม่ใช่ free-ed หรือ ลบ-ed) MTE ใช้ใน Android เพื่อตรวจจับการละเมิดกฎนี้ ซึ่งอ้างถึงในรายงานข้อขัดข้องว่าเป็นปัญหา "บัฟเฟอร์ล้น"/"บัฟเฟอร์น้อยเกินไป" และ "ใช้หลังจากใช้งานฟรี"

MTE มีสองโหมด: ซิงโครนัส (หรือ "ซิงค์") และอะซิงโครนัส (หรือ "async") แบบแรกทำงานช้ากว่าแต่ให้การวินิจฉัยที่แม่นยำยิ่งขึ้น อย่างหลังทำงานเร็วกว่าแต่ให้รายละเอียดได้โดยประมาณเท่านั้น เราจะกล่าวถึงทั้งสองกรณีแยกกัน เนื่องจากการวินิจฉัยจะแตกต่างกันเล็กน้อย

MTE โหมดซิงโครนัส

ในโหมดซิงโครนัส ("ซิงค์") ของ MTE SIGSEGV ขัดข้องด้วยรหัส 9 (SEGV_MTESERR)

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)

รายงานข้อขัดข้องของ MTE ทั้งหมดประกอบด้วย register dump และ backtrace ปกติสำหรับจุดที่ตรวจพบปัญหา บรรทัด "สาเหตุ:" สำหรับข้อผิดพลาดที่ MTE ตรวจพบจะมี "[MTE]" ดังตัวอย่างด้านบน พร้อมด้วยรายละเอียดเพิ่มเติม ในกรณีนี้ ข้อผิดพลาดเฉพาะที่ตรวจพบคือ "ใช้หลังจากใช้งานฟรี" และ "0 ไบต์ในการจัดสรร 32 ไบต์ที่ 0x7ae92853a0" บอกเราถึงขนาดและที่อยู่ของการจัดสรร และออฟเซ็ตในการจัดสรรที่เรา พยายามเข้าถึง

รายงานข้อขัดข้องของ MTE ยังรวมย้อนรอยเพิ่มเติมด้วย ไม่ใช่แค่จากจุดที่ตรวจพบเท่านั้น

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

ข้อผิดพลาด "Buffer Overflow" และ "Buffer Underflow" ให้เฉพาะแทร็กสแต็ก "จัดสรรโดย" เพิ่มเติมเท่านั้น เนื่องจากตามคำจำกัดความยังไม่ได้รับการจัดสรรคืน (หรือจะแสดงเป็น "ใช้หลังจากฟรี"):

Cause: [MTE]: Buffer Overflow, 0 bytes right of a 32-byte allocation at 0x7ae92853a0
[...]
backtrace:
[...]
allocated by thread 13949:

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

สาเหตุที่เป็นไปได้หลายประการ

บางครั้งรายงาน SEGV_MTESERR จะมีบรรทัดต่อไปนี้:

Note: multiple potential causes for this crash were detected, listing them in decreasing order of likelihood.

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

signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x400007b43063db5
backtrace:
    [stack...]

Note: multiple potential causes for this crash were detected, listing them in decreasing order of probability.

Cause: [MTE]: Use After Free, 5 bytes into a 10-byte allocation at 0x7b43063db0
deallocated by thread 6663:
    [stack...]
allocated by thread 6663:
    [stack...]

Cause: [MTE]: Use After Free, 5 bytes into a 6-byte allocation at 0x7b43063db0
deallocated by thread 6663:
    [stack...]

allocated by thread 6663:
    [stack...]

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

การวิเคราะห์พฤติกรรมการกำหนดสาเหตุโดยละเอียด

"สาเหตุ" ของความล้มเหลวควรแสดงการจัดสรรหน่วยความจำที่ตัวชี้ที่เข้าถึงได้รับมาแต่แรก น่าเสียดายที่ฮาร์ดแวร์ MTE ไม่มีวิธีแปลจากตัวชี้ที่มีแท็กไม่ตรงกันเป็นการจัดสรร เพื่ออธิบายข้อขัดข้องของ SEGV_MTESERR Android จะวิเคราะห์ข้อมูลต่อไปนี้:

  • ที่อยู่ข้อบกพร่อง (รวมถึงแท็กตัวชี้)
  • รายการการจัดสรรฮีปล่าสุดพร้อมการติดตามสแต็กและแท็กหน่วยความจำ
  • การจัดสรรปัจจุบัน (สด) ใกล้เคียงและแท็กหน่วยความจำ

หน่วยความจำที่ถูกจัดสรรเมื่อเร็วๆ นี้ ณ ที่อยู่ข้อบกพร่องโดยที่แท็กหน่วยความจำตรงกับแท็กที่อยู่ข้อบกพร่อง อาจเป็นสาเหตุ "ใช้หลังจากใช้งานฟรี"

หน่วยความจำสดใกล้เคียงใดๆ ที่แท็กหน่วยความจำตรงกับแท็กที่อยู่ข้อบกพร่อง อาจเป็นสาเหตุ "บัฟเฟอร์ล้น" (หรือ "บัฟเฟอร์อันเดอร์โฟลว์")

การจัดสรรที่อยู่ใกล้กับข้อบกพร่องมากขึ้น - ไม่ว่าจะในเวลาหรือในอวกาศ - ถือว่ามีแนวโน้มมากกว่าการจัดสรรที่อยู่ห่างไกล

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

ขอแนะนำให้นักพัฒนาแอปพิจารณาสาเหตุที่เป็นไปได้โดยเริ่มจากสาเหตุที่เป็นไปได้มากที่สุด มักจะเป็นเรื่องง่ายที่จะกรองสาเหตุที่ไม่เกี่ยวข้องออกโดยอิงตามการติดตามสแต็ก

MTE โหมดอะซิงโครนัส

ในโหมดอะซิงโครนัส ("async") ของ MTE SIGSEGV ขัดข้องด้วยรหัส 8 (SEGV_MTEAERR)

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

ข้อผิดพลาด SEGV_MTEAERR จะไม่รักษาที่อยู่หน่วยความจำ (จะแสดงเป็น "-------" เสมอ) Backtrace จะสอดคล้องกับช่วงเวลาที่ตรวจพบเงื่อนไข (เช่น ในการเรียกของระบบครั้งถัดไปหรือการสลับบริบทอื่นๆ) และไม่ใช่เมื่อมีการดำเนินการเข้าถึงที่ไม่ถูกต้อง

ซึ่งหมายความว่า backtrace "หลัก" ในข้อขัดข้อง MTE แบบอะซิงโครนัสมักจะ ไม่เกี่ยวข้อง ความล้มเหลวของโหมด Async จึงยากต่อการแก้ไขมากกว่าความล้มเหลวของโหมดการซิงค์ เป็นที่เข้าใจได้ดีที่สุดว่าเป็นการแสดงการมีอยู่ของข้อบกพร่องของหน่วยความจำในโค้ดใกล้เคียงในเธรดที่กำหนด บันทึกที่ด้านล่างของแฟ้มป้ายหลุมศพอาจบอกเป็นนัยถึงสิ่งที่เกิดขึ้นจริง มิฉะนั้น แนวทางปฏิบัติที่แนะนำคือการสร้างข้อผิดพลาดในโหมดซิงค์อีกครั้ง และใช้การวินิจฉัยที่ดีกว่าที่โหมดซิงค์มีให้!

หัวข้อขั้นสูง

ภายใต้ประทุน การแท็กหน่วยความจำทำงานโดยการกำหนดค่าแท็ก 4 บิต (0..15) แบบสุ่มให้กับการจัดสรรฮีปทุกครั้ง ค่านี้ถูกจัดเก็บไว้ในภูมิภาคเมทาดาทาพิเศษที่สอดคล้องกับหน่วยความจำฮีปที่จัดสรร ค่าเดียวกันนี้ถูกกำหนดให้กับไบต์ที่สำคัญที่สุดของตัวชี้ฮีปที่ส่งคืนจากฟังก์ชัน เช่น malloc() หรือตัวดำเนินการ new()

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

เนื่องจากค่าแท็กที่เป็นไปได้มีจำนวนจำกัด วิธีการนี้จึงน่าจะเป็นไปได้ ตำแหน่งหน่วยความจำใดๆ ที่ไม่ควรเข้าถึงด้วยตัวชี้ที่กำหนด เช่น อยู่นอกขอบเขต หรือหลังการจัดสรรคืน ("ตัวชี้ห้อย") มีแนวโน้มว่าจะมีค่าแท็กที่แตกต่างกัน และทำให้เกิดข้อขัดข้อง มีโอกาส ~7% ที่จะตรวจไม่พบจุดบกพร่องใดๆ แม้แต่ครั้งเดียว เนื่องจากค่าแท็กได้รับการกำหนดแบบสุ่ม จึงมีโอกาสประมาณ 93% อิสระที่จะตรวจพบจุดบกพร่องในครั้งต่อไปที่มันเกิดขึ้น

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

signal 11 (SIGSEGV), code 9 (SEGV_MTESERR), fault addr 0x0800007ae92853a0
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

ส่วน "แท็กหน่วยความจำ" พิเศษยังปรากฏในรายงานข้อขัดข้องซึ่งแสดงแท็กหน่วยความจำรอบๆ ที่อยู่ข้อบกพร่อง ในตัวอย่างด้านล่าง แท็กพอยน์เตอร์ "4" ไม่ตรงกับแท็กหน่วยความจำ "a"

Memory tags around the fault address (0x0400007b43063db5), one tag per 16 bytes:
  0x7b43063500: 0  f  0  2  0  f  0  a  0  7  0  8  0  7  0  e
  0x7b43063600: 0  9  0  8  0  5  0  e  0  f  0  c  0  f  0  4
  0x7b43063700: 0  b  0  c  0  b  0  2  0  1  0  4  0  7  0  8
  0x7b43063800: 0  b  0  c  0  3  0  a  0  3  0  6  0  b  0  a
  0x7b43063900: 0  3  0  4  0  f  0  c  0  3  0  e  0  0  0  c
  0x7b43063a00: 0  3  0  2  0  1  0  8  0  9  0  4  0  3  0  4
  0x7b43063b00: 0  5  0  2  0  5  0  a  0  d  0  6  0  d  0  2
  0x7b43063c00: 0  3  0  e  0  f  0  a  0  0  0  0  0  0  0  4
=>0x7b43063d00: 0  0  0  a  0  0  0  e  0  d  0 [a] 0  f  0  e
  0x7b43063e00: 0  7  0  c  0  9  0  a  0  d  0  2  0  0  0  c
  0x7b43063f00: 0  0  0  6  0  b  0  8  0  3  0  0  0  5  0  e
  0x7b43064000: 0  d  0  2  0  7  0  a  0  7  0  a  0  d  0  8
  0x7b43064100: 0  b  0  2  0  b  0  4  0  1  0  6  0  d  0  4
  0x7b43064200: 0  1  0  6  0  f  0  2  0  f  0  6  0  5  0  c
  0x7b43064300: 0  1  0  4  0  d  0  6  0  f  0  e  0  1  0  8
  0x7b43064400: 0  f  0  4  0  3  0  2  0  1  0  2  0  5  0  6

ส่วนของหลุมฝังศพที่แสดงเนื้อหาหน่วยความจำรอบๆ ค่าลงทะเบียนทั้งหมดจะแสดงค่าแท็กด้วย

memory near x10 ([anon:scudo:primary]):
0000007b4304a000 7e82000000008101 000003e9ce8b53a0  .......~.S......
0700007b4304a010 0000200000006001 0000000000000000  .`... ..........
0000007b4304a020 7c03000000010101 000003e97c61071e  .......|..a|....
0200007b4304a030 0c00007b4304a270 0000007ddc4fedf8  p..C{.....O.}...
0000007b4304a040 84e6000000008101 000003e906f7a9da  ................
0300007b4304a050 ffffffff00000042 0000000000000000  B...............
0000007b4304a060 8667000000010101 000003e9ea858f9e  ......g.........
0400007b4304a070 0000000100000001 0000000200000002  ................
0000007b4304a080 f5f8000000010101 000003e98a13108b  ................
0300007b4304a090 0000007dd327c420 0600007b4304a2b0   .'.}......C{...
0000007b4304a0a0 88ca000000010101 000003e93e5e5ac5  .........Z^>....
0a00007b4304a0b0 0000007dcc4bc500 0300007b7304cb10  ..K.}......s{...
0000007b4304a0c0 0f9c000000010101 000003e9e1602280  ........."`.....
0900007b4304a0d0 0000007dd327c780 0700007b7304e2d0  ..'.}......s{...
0000007b4304a0e0 0d1d000000008101 000003e906083603  .........6......
0a00007b4304a0f0 0000007dd327c3b8 0000000000000000  ..'.}...........