ตั้งแต่ปี 2016 เป็นต้นมา ช่องโหว่ทั้งหมดใน Android ประมาณ 86% เกี่ยวข้องกับความปลอดภัยของหน่วยความจำ ผู้โจมตีใช้ช่องโหว่ส่วนใหญ่โดยการเปลี่ยนโฟลว์การควบคุมปกติของแอปเพื่อทํากิจกรรมที่เป็นอันตรายโดยพลการด้วยสิทธิ์ทั้งหมดของแอปที่มีช่องโหว่ ความสมบูรณ์ของโฟลว์การควบคุม (CFI) เป็นกลไกด้านความปลอดภัยที่ไม่อนุญาตให้เปลี่ยนแปลงกราฟโฟลว์การควบคุมเดิมของไบนารีที่คอมไพล์แล้ว ซึ่งทําให้การโจมตีดังกล่าวทำได้ยากขึ้นอย่างมาก
ใน Android 8.1 เราได้เปิดใช้การใช้งาน CFI ของ LLVM ในสแต็กสื่อ ใน Android 9 เราได้เปิดใช้ CFI ในคอมโพเนนต์และเคอร์เนลเพิ่มเติม CFI ของระบบจะเปิดอยู่โดยค่าเริ่มต้น แต่คุณต้องเปิดใช้ CFI ของเคอร์เนล
CFI ของ LLVM ต้องคอมไพล์ด้วยการเพิ่มประสิทธิภาพเวลาลิงก์ (LTO) LTO จะเก็บการแสดงบิตโค้ด LLVM ของไฟล์ออบเจ็กต์ไว้จนกว่าจะถึงเวลาลิงก์ ซึ่งช่วยให้คอมไพเลอร์พิจารณาได้ดีขึ้นว่าจะดำเนินการเพิ่มประสิทธิภาพใดได้บ้าง การเปิดใช้ LTO จะลดขนาดไบนารีสุดท้ายและปรับปรุง ประสิทธิภาพ แต่จะเพิ่มเวลาคอมไพล์ ในการทดสอบบน Android การรวม LTO และ CFI ส่งผลให้มีค่าใช้จ่ายที่มองข้ามได้ต่อขนาดโค้ดและประสิทธิภาพ ในบางกรณีทั้ง 2 อย่างได้รับการปรับปรุง
ดูรายละเอียดทางเทคนิคเพิ่มเติมเกี่ยวกับ CFI และวิธีจัดการการตรวจสอบการควบคุมการส่งต่ออื่นๆ ได้ที่เอกสารประกอบการออกแบบ LLVM
ตัวอย่างและแหล่งที่มา
CFI จัดทำโดยคอมไพเลอร์และเพิ่มการตรวจสอบลงในไบนารีระหว่าง เวลาคอมไพล์ เราสนับสนุน CFI ในชุดเครื่องมือ Clang และระบบบิลด์ Android ใน AOSP
CFI จะเปิดใช้โดยค่าเริ่มต้นสำหรับอุปกรณ์ Arm64 สำหรับชุดคอมโพเนนต์ใน
/platform/build/target/product/cfi-common.mk
นอกจากนี้ยังเปิดใช้โดยตรงในไฟล์ makefile/blueprint ของชุดคอมโพเนนต์สื่อ เช่น /platform/frameworks/av/media/libmedia/Android.bp
และ /platform/frameworks/av/cmds/stagefright/Android.mk
การใช้ CFI ของระบบ
ระบบจะเปิดใช้ CFI โดยค่าเริ่มต้นหากคุณใช้ Clang และระบบบิลด์ของ Android เนื่องจาก CFI ช่วยให้ผู้ใช้ Android ปลอดภัย คุณจึงไม่ควรปิดใช้
เราขอแนะนำอย่างยิ่งให้คุณเปิดใช้ CFI สำหรับคอมโพเนนต์เพิ่มเติม โค้ดแบบเนทีฟที่มีสิทธิ์พิเศษหรือโค้ดแบบเนทีฟที่ประมวลผลข้อมูลจากผู้ใช้ที่ไม่น่าเชื่อถือเป็นตัวเลือกที่เหมาะสม หากใช้ clang และระบบบิลด์ Android คุณจะเปิดใช้ CFI ในคอมโพเนนต์ใหม่ได้โดยเพิ่มโค้ด 2-3 บรรทัดลงในไฟล์ makefile หรือไฟล์ Blueprint
การรองรับ CFI ใน Makefile
หากต้องการเปิดใช้ CFI ในไฟล์ Make เช่น /platform/frameworks/av/cmds/stagefright/Android.mk ให้
เพิ่มสิ่งต่อไปนี้
LOCAL_SANITIZE := cfi # Optional features LOCAL_SANITIZE_DIAG := cfi LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
LOCAL_SANITIZEระบุ CFI เป็นตัวทำความสะอาดระหว่างการ บิลด์LOCAL_SANITIZE_DIAGเปิดโหมดการวินิจฉัยสำหรับ CFI โหมดการวินิจฉัยจะพิมพ์ข้อมูลการแก้ไขข้อบกพร่องเพิ่มเติมใน Logcat ระหว่างที่เกิดข้อขัดข้อง ซึ่งมีประโยชน์ขณะพัฒนาและทดสอบบิลด์ โปรดอย่าลืมนำโหมดการวินิจฉัยออกจากบิลด์เวอร์ชันที่ใช้งานจริงLOCAL_SANITIZE_BLACKLISTช่วยให้คอมโพเนนต์เลือก ปิดใช้การตรวจสอบ CFI สำหรับฟังก์ชันหรือไฟล์แหล่งที่มาแต่ละรายการได้ คุณใช้บัญชีดำเป็นทางเลือกสุดท้ายเพื่อแก้ไขปัญหาที่ผู้ใช้พบ ซึ่งอาจเกิดขึ้นได้ ดูรายละเอียดเพิ่มเติมได้ที่ การปิดใช้ CFI
การรองรับ CFI ในไฟล์พิมพ์เขียว
หากต้องการเปิดใช้ CFI ในไฟล์พิมพ์เขียว เช่น /platform/frameworks/av/media/libmedia/Android.bp ให้
เพิ่มข้อความต่อไปนี้
sanitize: {
cfi: true,
diag: {
cfi: true,
},
blacklist: "cfi_blacklist.txt",
},การแก้ปัญหา
หากเปิดใช้ CFI ในคอมโพเนนต์ใหม่ คุณอาจพบปัญหาบางอย่างเกี่ยวกับข้อผิดพลาดประเภทฟังก์ชันไม่ตรงกันและข้อผิดพลาดประเภทโค้ดแอสเซมบลีไม่ตรงกัน
ข้อผิดพลาดประเภทฟังก์ชันไม่ตรงกันเกิดขึ้นเนื่องจาก CFI จำกัดการเรียกแบบอ้อมให้เฉพาะ การข้ามไปยังฟังก์ชันที่มีประเภทแบบไดนามิกเหมือนกับประเภทแบบคงที่ที่ใช้ในการ เรียก CFI จำกัดการเรียกใช้ฟังก์ชันสมาชิกเสมือนและที่ไม่ใช่เสมือนให้ข้ามไปยังออบเจ็กต์ที่เป็นคลาสที่ได้มาจากประเภทแบบคงที่ของออบเจ็กต์ที่ใช้ในการเรียกใช้ฟังก์ชันเท่านั้น ซึ่งหมายความว่าเมื่อคุณมีโค้ดที่ละเมิดสมมติฐานใดสมมติฐานหนึ่งเหล่านี้ เครื่องมือที่ CFI เพิ่มจะหยุดทำงาน ตัวอย่างเช่น สแต็กเทรซแสดง SIGABRT และ Logcat มีบรรทัดเกี่ยวกับความสมบูรณ์ของโฟลว์การควบคุมที่พบว่าไม่ตรงกัน
หากต้องการแก้ไขปัญหานี้ ให้ตรวจสอบว่าฟังก์ชันที่เรียกใช้มีประเภทเดียวกันกับที่ประกาศแบบคงที่ ตัวอย่าง CL 2 รายการมีดังนี้
อีกปัญหาที่อาจเกิดขึ้นคือการพยายามเปิดใช้ CFI ในโค้ดที่มีการเรียกใช้แอสเซมบลีโดยอ้อม เนื่องจากไม่ได้พิมพ์โค้ดแอสเซมบลี จึงทำให้เกิดการไม่ตรงกันของประเภท
หากต้องการแก้ไขปัญหานี้ ให้สร้าง Wrapper โค้ดแบบเนทีฟสำหรับการเรียกแต่ละรายการใน Assembly และกำหนดลายเซ็นฟังก์ชันเดียวกันให้กับ Wrapper เหมือนกับพอยน์เตอร์ที่เรียก จากนั้น Wrapper จะ เรียกใช้โค้ดแอสเซมบลีได้โดยตรง เนื่องจาก CFI ไม่ได้ใช้สาขาโดยตรง (จึงไม่สามารถเปลี่ยนเส้นทางใหม่ได้ในรันไทม์และไม่ก่อให้เกิดความเสี่ยงด้านความปลอดภัย) การดำเนินการนี้จะแก้ไขปัญหา
หากมีฟังก์ชันการประกอบมากเกินไปและแก้ไขทั้งหมดไม่ได้ คุณสามารถ ขึ้นบัญชีดำฟังก์ชันทั้งหมดที่มีการเรียกการประกอบโดยอ้อมได้ด้วย เราไม่แนะนำให้ทำเช่นนี้เนื่องจากจะปิดใช้การตรวจสอบ CFI ในฟังก์ชันเหล่านี้ ซึ่งจะเปิด พื้นผิวการโจมตี
การปิดใช้ CFI
เราไม่พบค่าใช้จ่ายด้านประสิทธิภาพใดๆ คุณจึงไม่จำเป็นต้องปิดใช้ CFI อย่างไรก็ตาม หากมีผลกระทบต่อผู้ใช้ คุณสามารถเลือกปิดใช้ CFI สำหรับฟังก์ชันหรือไฟล์ต้นฉบับแต่ละรายการได้โดยระบุไฟล์บัญชีดำของ Sanitizer ในเวลาคอมไพล์ บัญชีดำจะสั่งให้คอมไพเลอร์ปิดใช้การตรวจสอบ CFI ในตำแหน่งที่ระบุ
ระบบบิลด์ของ Android รองรับแบล็กลิสต์ต่อคอมโพเนนต์ (ช่วยให้คุณเลือกไฟล์ต้นฉบับหรือฟังก์ชันแต่ละรายการที่จะไม่ได้รับการตรวจสอบ CFI) สำหรับทั้ง Make และ Soong ดูรายละเอียดเพิ่มเติมเกี่ยวกับรูปแบบของ ไฟล์บัญชีดำได้ที่เอกสาร Clang ต้นทาง
การตรวจสอบ
ปัจจุบันยังไม่มีการทดสอบ CTS สำหรับ CFI โดยเฉพาะ แต่ให้ตรวจสอบว่าการทดสอบ CTS ผ่านโดยเปิดหรือปิดใช้ CFI เพื่อยืนยันว่า CFI ไม่ส่งผลกระทบต่ออุปกรณ์