ระบบจะทําเครื่องหมายส่วนโค้ดที่เรียกใช้ได้สําหรับไบนารีของระบบ 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 ของกลุ่ม