หน่วยความจำแบบเรียกใช้ได้อย่างเดียว (XOM) สำหรับไบนารี AArch64

ระบบจะทําเครื่องหมายส่วนโค้ดที่เรียกใช้ได้สําหรับไบนารีของระบบ AArch64 เป็น "เรียกใช้ได้อย่างเดียว" (อ่านไม่ได้) โดยค่าเริ่มต้น เพื่อลดความเสี่ยงจากการโจมตีด้วยโค้ดแบบทันท่วงที โค้ดที่ผสมข้อมูลและโค้ดเข้าด้วยกันและโค้ดที่ตั้งใจตรวจสอบส่วนเหล่านี้ (โดยไม่แมปกลุ่มหน่วยความจำใหม่ให้อ่านได้ก่อน) จะใช้งานไม่ได้อีกต่อไป แอปที่มี SDK เป้าหมายเป็น 10 (API ระดับ 29 ขึ้นไป) จะได้รับผลกระทบหากแอปพยายามอ่านส่วนโค้ดของไลบรารีระบบที่เปิดใช้หน่วยความจำแบบเรียกใช้ได้อย่างเดียว (XOM) ในหน่วยความจำโดยไม่ได้ทำเครื่องหมายส่วนดังกล่าวว่าอ่านได้ก่อน

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

การใช้งาน

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

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

การรองรับและผลกระทบต่ออุปกรณ์

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

ต้องตั้งค่า Flag ของเคอร์เนล CONFIG_ARM64_UAO ในเคอร์เนลเพื่อให้แน่ใจว่าเคอร์เนลจะเคารพหน้าใน Userland ที่ทำเครื่องหมายว่า "เรียกใช้ได้อย่างเดียว" อุปกรณ์ ARMv8 รุ่นเก่าหรืออุปกรณ์ ARMv8.2 ที่ปิดใช้การลบล้างการเข้าถึงของผู้ใช้ (UAO) อาจไม่ได้รับประโยชน์อย่างเต็มที่จากการดำเนินการนี้และอาจยังอ่านหน้าสำหรับเรียกใช้ได้โดยใช้ syscall

ปรับโค้ดที่มีอยู่

โค้ดที่ย้ายมาจาก AArch32 อาจมีการผสมข้อมูลและโค้ดเข้าด้วยกัน ซึ่งทำให้เกิดปัญหา ในหลายกรณี การแก้ไขปัญหาเหล่านี้ทำได้ง่ายๆ ด้วยการย้ายค่าคงที่ไปยังส่วน .data ในไฟล์แอสเซมบลี

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

ตัวอย่าง

ไฟล์ไบนารีที่คอมไพเลอร์ Clang สร้างขึ้นจะไม่มีปัญหากับการผสมข้อมูลไว้ในโค้ด หากรวมโค้ดที่คอมไพเลอร์ GNU (GCC) สร้างขึ้น (จากไลบรารีแบบคงที่) ให้ตรวจสอบไบนารีเอาต์พุตเพื่อให้แน่ใจว่าไม่มีการจัดกลุ่มค่าคงที่ไว้ในส่วนโค้ด

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

เปิดใช้ XOM

ระบบจะเปิดใช้ "เรียกใช้ได้อย่างเดียว" โดยค่าเริ่มต้นสำหรับไบนารี 64 บิตทั้งหมดในระบบการสร้าง

ปิดใช้ XOM

คุณสามารถปิดใช้ "เรียกใช้อย่างเดียว" ที่ระดับโมดูล ตามลําดับชั้นของไดเรกทอรีย่อยทั้งหมด หรือทั่วโลกสําหรับทั้งบิลด์

คุณสามารถปิดใช้ XOM สำหรับแต่ละโมดูลที่ไม่สามารถปรับโครงสร้างใหม่ได้ หรือต้องอ่านโค้ดที่เรียกใช้ได้ โดยการตั้งค่าตัวแปร LOCAL_XOM และ xom เป็น false

// Android.mk
LOCAL_XOM := false

// Android.bp
cc_binary { // or other module types
   ...
   xom: false,
}

หากปิดใช้หน่วยความจำแบบเรียกใช้ได้อย่างเดียวในไลบรารีแบบคงที่ ระบบการสร้างจะใช้ตัวเลือกนี้กับโมดูลทั้งหมดที่ขึ้นอยู่กับไลบรารีแบบคงที่นั้น คุณลบล้างค่านี้ได้โดยใช้ xom: true,

หากต้องการปิดใช้หน่วยความจำแบบเรียกใช้ได้อย่างเดียวในไดเรกทอรีย่อยที่เฉพาะเจาะจง (เช่น foo/bar/) ให้ส่งค่าไปยัง XOM_EXCLUDE_PATHS

make -j XOM_EXCLUDE_PATHS=foo/bar

หรือจะตั้งค่าPRODUCT_XOM_EXCLUDE_PATHSตัวแปรในการกําหนดค่าผลิตภัณฑ์ก็ได้

คุณสามารถปิดใช้ไบนารีแบบเรียกใช้ได้อย่างเดียวทั่วโลกโดยส่ง ENABLE_XOM=false ไปยังคําสั่ง make

make -j ENABLE_XOM=false

การตรวจสอบความถูกต้อง

ไม่มี CTS หรือการทดสอบการยืนยันสําหรับหน่วยความจําแบบเรียกใช้ได้อย่างเดียว คุณสามารถยืนยันไบนารีด้วยตนเองได้โดยใช้ readelf และตรวจสอบ Flag ของกลุ่ม