ตั้งแต่ปี 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 ใน Toolchain ของ 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 ไม่ส่งผลกระทบต่ออุปกรณ์