หน่วยความจำเฉพาะการดำเนินการ (XOM) สำหรับไบนารี AArch64

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

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

การนำไปปฏิบัติ

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

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

การสนับสนุนอุปกรณ์และผลกระทบ

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

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

การรีแฟคเตอร์โค้ดที่มีอยู่

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

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

ตัวอย่าง:

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

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

กำลังเปิดใช้งาน

การดำเนินการเท่านั้นถูกเปิดใช้งานตามค่าเริ่มต้นสำหรับไบนารี 64 บิตทั้งหมดในระบบบิลด์

กำลังปิดการใช้งาน

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

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 และตรวจสอบแฟล็กส่วน