ในปี 2559 ช่องโหว่ประมาณ 86% บน Android เกี่ยวข้องกับความปลอดภัยของหน่วยความจำ ช่องโหว่ส่วนใหญ่ถูกโจมตีโดยผู้โจมตีที่เปลี่ยนโฟลว์การควบคุมปกติของแอปพลิเคชันเพื่อดำเนินกิจกรรมที่เป็นอันตรายตามอำเภอใจด้วยสิทธิ์ทั้งหมดของแอปพลิเคชันที่ถูกโจมตี ความสมบูรณ์ของกระแสการควบคุม (CFI) เป็นกลไกการรักษาความปลอดภัยที่ไม่อนุญาตการเปลี่ยนแปลงกราฟการควบคุมดั้งเดิมของไบนารีที่คอมไพล์แล้ว ทำให้การโจมตีดังกล่าวทำได้ยากขึ้นอย่างมาก
ใน Android 8.1 เราเปิดใช้งานการใช้งาน CFI ของ LLVM ในกองสื่อ ใน Android 9 เราเปิดใช้งาน CFI ในส่วนประกอบเพิ่มเติมและเคอร์เนลด้วย CFI ระบบเปิดอยู่โดยค่าเริ่มต้น แต่คุณต้องเปิดใช้งานเคอร์เนล CFI
CFI ของ LLVM ต้องคอมไพล์ด้วย Link-Time Optimization (LTO) LTO จะคงการแสดงบิตโค้ด LLVM ของไฟล์อ็อบเจ็กต์ไว้จนถึงเวลาลิงก์ ซึ่งช่วยให้คอมไพเลอร์ให้เหตุผลที่ดีขึ้นเกี่ยวกับสิ่งที่สามารถทำการปรับให้เหมาะสมที่สุดได้ การเปิดใช้งาน LTO จะลดขนาดของไบนารีสุดท้ายและปรับปรุงประสิทธิภาพ แต่จะเพิ่มเวลาในการคอมไพล์ ในการทดสอบบน Android การรวมกันของ LTO และ CFI ส่งผลให้ค่าใช้จ่ายและประสิทธิภาพของโค้ดลดลงเล็กน้อย ในบางกรณีทั้งคู่ก็ดีขึ้น
สำหรับรายละเอียดทางเทคนิคเพิ่มเติมเกี่ยวกับ CFI และวิธีจัดการการตรวจสอบการควบคุมการส่งต่อ ดู เอกสารการออกแบบ LLVM
ตัวอย่างและที่มา
CFI จัดทำโดยคอมไพเลอร์และเพิ่มเครื่องมือวัดลงในไบนารีระหว่างเวลาคอมไพล์ เรารองรับ CFI ใน Clang toolchain และระบบ Android build ใน AOSP
CFI ถูกเปิดใช้งานโดยค่าเริ่มต้นสำหรับอุปกรณ์ Arm64 สำหรับชุดของส่วนประกอบใน /platform/build/target/product/cfi-common.mk
นอกจากนี้ยังเปิดใช้งานโดยตรงในชุดไฟล์ makefiles/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 ในส่วนประกอบใหม่ได้โดยเพิ่มสองสามบรรทัดใน makefiles หรือไฟล์พิมพ์เขียวของคุณ
รองรับ CFI ใน makefiles
หากต้องการเปิดใช้งาน 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 สองตัวอย่าง:
- บลูทูธ : /c/platform/system/bt/+/532377
- NFC : /c/platform/system/nfc/+/527858
ปัญหาที่เป็นไปได้อีกประการหนึ่งคือการพยายามเปิดใช้งาน CFI ในโค้ดที่มีการเรียกทางอ้อมไปยังแอสเซมบลี เนื่องจากไม่ได้พิมพ์รหัสแอสเซมบลี ส่งผลให้ประเภทไม่ตรงกัน
ในการแก้ไขปัญหานี้ ให้สร้าง wrapper โค้ดเนทีฟสำหรับการเรียกแอสเซมบลีแต่ละครั้ง และกำหนดลายเซ็นฟังก์ชันเดียวกันกับตัวระบุการเรียก เสื้อคลุมสามารถเรียกรหัสชุดประกอบได้โดยตรง เนื่องจาก CFI ไม่ได้กำหนดสาขาโดยตรง (ไม่สามารถชี้ใหม่ได้ในขณะใช้งานจริง ดังนั้นจึงไม่ก่อให้เกิดความเสี่ยงด้านความปลอดภัย) การดำเนินการนี้จะแก้ไขปัญหาได้
หากมีฟังก์ชันแอสเซมบลีมากเกินไปและไม่สามารถแก้ไขได้ทั้งหมด คุณยังสามารถขึ้นบัญชีดำฟังก์ชันทั้งหมดที่มีการเรียกแอสเซมบลีทางอ้อม ไม่แนะนำเนื่องจากจะปิดใช้งานการตรวจสอบ CFI ในฟังก์ชันเหล่านี้ ซึ่งจะทำให้พื้นผิวการโจมตีเปิดขึ้น
ปิดการใช้งาน CFI
เราไม่ได้สังเกตค่าใช้จ่ายด้านประสิทธิภาพใดๆ ดังนั้นคุณจึงไม่ควรปิด CFI อย่างไรก็ตาม หากมีผลกระทบกับผู้ใช้ คุณสามารถเลือกปิดใช้งาน CFI สำหรับแต่ละฟังก์ชันหรือไฟล์ต้นทางได้ โดยการจัดหาไฟล์บัญชีดำของโปรแกรมฆ่าเชื้อในเวลาคอมไพล์ บัญชีดำสั่งให้คอมไพเลอร์ปิดใช้งานเครื่องมือ CFI ในตำแหน่งที่ระบุ
ระบบบิลด์ Android ให้การสนับสนุนบัญชีดำสำหรับแต่ละองค์ประกอบ (ทำให้คุณสามารถเลือกไฟล์ต้นฉบับหรือฟังก์ชันแต่ละรายการที่จะไม่ได้รับการวัด CFI) สำหรับทั้งยี่ห้อและรุ่น สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับรูปแบบของไฟล์บัญชีดำ โปรดดู เอกสารต้นน้ำ Clang
การตรวจสอบความถูกต้อง
ปัจจุบันไม่มีการทดสอบ CTS สำหรับ CFI โดยเฉพาะ ตรวจสอบให้แน่ใจว่าการทดสอบ CTS ผ่านโดยมีหรือไม่มีการเปิดใช้งาน CFI เพื่อตรวจสอบว่า CFI ไม่ส่งผลกระทบต่ออุปกรณ์