ในปี 2016 ช่องโหว่ทั้งหมดประมาณ 86% ใน Android เกี่ยวข้องกับความปลอดภัยของหน่วยความจำ ผู้โจมตีจะใช้ประโยชน์จากช่องโหว่ส่วนใหญ่โดยเปลี่ยนโฟลว์การควบคุมปกติของแอปเพื่อดําเนินการที่เป็นอันตรายตามต้องการโดยมีสิทธิ์ทั้งหมดของแอปที่ถูกโจมตี ความสมบูรณ์ของโฟลว์การควบคุม (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 เนื่องจาก CFI ช่วยดูแลให้ผู้ใช้ Android ปลอดภัย
เราขอแนะนําอย่างยิ่งให้คุณเปิดใช้ CFI สําหรับคอมโพเนนต์เพิ่มเติม โค้ดเนทีฟที่มีสิทธิ์หรือโค้ดเนทีฟที่ประมวลผลข้อมูลที่ผู้ใช้ป้อนซึ่งไม่น่าเชื่อถือเหมาะจะเป็นโค้ดที่จะใช้ หากใช้ clang และระบบการสร้างของ Android คุณจะเปิดใช้ CFI ในคอมโพเนนต์ใหม่ได้โดยเพิ่มบรรทัดลงในไฟล์ make หรือไฟล์ BluePrint
การรองรับ CFI ในไฟล์ make
หากต้องการเปิดใช้ 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 เพิ่มจะหยุดทำงาน ตัวอย่างเช่น StackTrace แสดง SIGABRT และ logcat มีบรรทัดเกี่ยวกับความสมบูรณ์ของโฟลว์การควบคุมที่พบว่าไม่ตรงกัน
วิธีแก้ไขคือตรวจสอบว่าฟังก์ชันที่เรียกใช้มีประเภทเดียวกับที่ประกาศแบบคงที่ ตัวอย่าง CL 2 รายการมีดังนี้
ปัญหาที่อาจเกิดขึ้นอีกอย่างหนึ่งคือการพยายามเปิดใช้ CFI ในโค้ดที่มีคำเรียกใช้แอสเซมบลีโดยอ้อม เนื่องจากไม่ได้พิมพ์โค้ดแอสเซมบลี จึงทำให้ประเภทไม่ตรงกัน
วิธีแก้ไขคือสร้างตัวห่อโค้ดเนทีฟสำหรับการเรียกใช้แอสเซมบลีแต่ละรายการ และให้ลายเซ็นฟังก์ชันของตัวแฮนเดิลเหมือนกับตัวชี้การเรียกใช้ จากนั้น Wrapper จะเรียกใช้โค้ดแอสเซมบลีได้โดยตรง เนื่องจาก CFI ไม่ได้ติดตั้งใช้งานสาขาโดยตรง (ไม่สามารถเปลี่ยนเส้นทางใหม่เมื่อรันไทม์ จึงไม่มีความเสี่ยงด้านความปลอดภัย) วิธีนี้จึงช่วยแก้ปัญหาได้
หากมีฟังก์ชันการประกอบมากเกินไปและไม่สามารถแก้ไขได้ทั้งหมด คุณยังเพิ่มฟังก์ชันทั้งหมดที่มีการเรียกใช้การประกอบโดยอ้อมลงในรายการที่อนุญาตไม่ได้ได้ด้วย เราไม่แนะนำให้ใช้วิธีนี้เนื่องจากจะปิดใช้การตรวจสอบ CFI ในฟังก์ชันเหล่านี้ ซึ่งจะเปิดช่องโหว่ให้เกิดการโจมตี
การปิดใช้ CFI
เราไม่พบค่าใช้จ่ายเพิ่มเติมด้านประสิทธิภาพ คุณจึงไม่จำเป็นต้องปิดใช้ CFI อย่างไรก็ตาม หากมีผลกระทบต่อผู้ใช้ คุณจะปิดใช้ CFI เฉพาะสำหรับฟังก์ชันหรือไฟล์ต้นฉบับแต่ละรายการได้โดยระบุไฟล์รายการที่อนุญาตของโปรแกรมตรวจสอบที่เวลาคอมไพล์ รายการที่อนุญาตจะสั่งให้คอมไพเลอร์ปิดใช้เครื่องมือวัด CFI ในตำแหน่งที่ระบุ
ระบบบิลด์ของ Android รองรับรายการที่บล็อกสำหรับแต่ละคอมโพเนนต์ (ช่วยให้คุณเลือกไฟล์ต้นฉบับหรือฟังก์ชันแต่ละรายการที่จะไม่ได้รับการตรวจสอบ CFI) สำหรับทั้ง Make และ Soong ดูรายละเอียดเพิ่มเติมเกี่ยวกับรูปแบบของไฟล์รายการที่บล็อกได้ที่เอกสาร Clang ต้นทาง
การตรวจสอบความถูกต้อง
ปัจจุบันยังไม่มีการทดสอบ CTS สำหรับ CFI โดยเฉพาะ แต่ให้ตรวจสอบว่าการทดสอบ CTS ผ่านหรือไม่ไม่ว่าจะเปิดใช้ CFI หรือไม่ เพื่อยืนยันว่า CFI ไม่ได้ส่งผลต่ออุปกรณ์