ควบคุมความสมบูรณ์ของโฟลว์

ในปี 2559 ประมาณ 86% ของช่องโหว่ทั้งหมดบน Android เกี่ยวข้องกับความปลอดภัยของหน่วยความจำ ช่องโหว่ส่วนใหญ่ถูกโจมตีโดยผู้โจมตีที่เปลี่ยนขั้นตอนการควบคุมปกติของแอปพลิเคชันเพื่อดำเนินกิจกรรมที่เป็นอันตรายตามอำเภอใจพร้อมสิทธิพิเศษทั้งหมดของแอปพลิเคชันที่ถูกโจมตี Control Flow Integrity (CFI) เป็นกลไกด้านความปลอดภัยที่ไม่อนุญาตให้มีการเปลี่ยนแปลงกราฟการควบคุมโฟลว์ดั้งเดิมของไบนารีที่คอมไพล์แล้ว ทำให้การโจมตีดังกล่าวทำได้ยากขึ้นอย่างมาก

ใน Android 8.1 เราเปิดใช้งานการนำ CFI ของ LLVM ไปใช้ในกลุ่มสื่อ ใน Android 9 เราเปิดใช้งาน CFI ในส่วนประกอบเพิ่มเติมและเคอร์เนลด้วย System CFI เปิดอยู่ตามค่าเริ่มต้น แต่คุณต้องเปิดใช้งาน Kernel CFI

CFI ของ LLVM จำเป็นต้องคอมไพล์ด้วย Link-Time Optimization (LTO) LTO จะรักษาการแสดงบิตโค้ด LLVM ของไฟล์อ็อบเจ็กต์ไว้จนกระทั่งถึงเวลาลิงก์ ซึ่งช่วยให้คอมไพลเลอร์ให้เหตุผลได้ดีขึ้นเกี่ยวกับการปรับปรุงประสิทธิภาพที่สามารถทำได้ การเปิดใช้งาน LTO จะลดขนาดของไบนารีสุดท้ายและปรับปรุงประสิทธิภาพ แต่จะเพิ่มเวลาในการคอมไพล์ ในการทดสอบบน Android การรวมกันของ LTO และ CFI ส่งผลให้มีค่าใช้จ่ายเล็กน้อยต่อขนาดและประสิทธิภาพของโค้ด ในบางกรณีก็ดีขึ้นทั้งคู่

สำหรับรายละเอียดทางเทคนิคเพิ่มเติมเกี่ยวกับ CFI และวิธีจัดการการตรวจสอบควบคุมไปข้างหน้าอื่นๆ โปรดดู เอกสารประกอบการออกแบบ LLVM

ตัวอย่างและที่มา

คอมไพเลอร์จัดทำ CFI และเพิ่มเครื่องมือวัดลงในไบนารีในช่วงเวลาคอมไพล์ เรารองรับ CFI ใน Clang toolchain และระบบบิลด์ Android ใน AOSP

CFI ถูกเปิดใช้งานตามค่าเริ่มต้นสำหรับอุปกรณ์ Arm64 สำหรับชุดส่วนประกอบใน /platform/build/target/product/cfi-common.mk นอกจากนี้ยังเปิดใช้งานโดยตรงในชุดไฟล์ makefiles/พิมพ์เขียวของส่วนประกอบสื่อ เช่น /platform/frameworks/av/media/libmedia/Android.bp และ /platform/frameworks/av/cmds/stagefright/Android.mk

การนำระบบ CFI ไปใช้

CFI ถูกเปิดใช้งานตามค่าเริ่มต้นหากคุณใช้ Clang และระบบบิลด์ Android เนื่องจาก CFI ช่วยให้ผู้ใช้ Android ปลอดภัย คุณจึงไม่ควรปิดใช้งาน

เราขอแนะนำให้คุณเปิดใช้งาน CFI สำหรับส่วนประกอบเพิ่มเติม ตัวเลือกที่เหมาะสมคือโค้ดเนทีฟที่มีสิทธิพิเศษ หรือโค้ดเนทีฟที่ประมวลผลอินพุตของผู้ใช้ที่ไม่น่าเชื่อถือ หากคุณใช้เสียงดังกราวและระบบบิลด์ Android คุณสามารถเปิดใช้งาน CFI ในส่วนประกอบใหม่ได้โดยการเพิ่มสองสามบรรทัดในไฟล์ makefile หรือพิมพ์เขียวของคุณ

รองรับ 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 สองรายการ:

ปัญหาที่เป็นไปได้อีกประการหนึ่งกำลังพยายามเปิดใช้งาน CFI ในโค้ดที่มีการเรียกทางอ้อมไปยังแอสเซมบลี เนื่องจากไม่ได้พิมพ์รหัสแอสเซมบลี จึงส่งผลให้ประเภทไม่ตรงกัน

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

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

ปิดการใช้งาน CFI

เราไม่ได้สังเกตค่าใช้จ่ายด้านประสิทธิภาพใดๆ ดังนั้นคุณจึงไม่จำเป็นต้องปิดการใช้งาน CFI อย่างไรก็ตาม หากมีผลกระทบต่อผู้ใช้ คุณสามารถเลือกปิดการใช้งาน CFI สำหรับแต่ละฟังก์ชันหรือไฟล์ต้นฉบับได้โดยการจัดหาไฟล์บัญชีดำของน้ำยาฆ่าเชื้อในเวลารวบรวม บัญชีดำแนะนำให้คอมไพเลอร์ปิดการใช้งานเครื่องมือวัด CFI ในตำแหน่งที่ระบุ

ระบบบิลด์ Android ให้การสนับสนุนบัญชีดำต่อองค์ประกอบ (อนุญาตให้คุณเลือกไฟล์ต้นฉบับหรือฟังก์ชันแต่ละรายการที่จะไม่ได้รับเครื่องมือ CFI) สำหรับทั้ง Make และ Soong สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับรูปแบบของไฟล์บัญชีดำ โปรดดู เอกสารอัปสตรีม Clang

การตรวจสอบ

ปัจจุบันไม่มีการทดสอบ CTS สำหรับ CFI โดยเฉพาะ โปรดตรวจสอบให้แน่ใจว่าการทดสอบ CTS ผ่านหรือไม่เปิดใช้งาน CFI เพื่อตรวจสอบว่า CFI จะไม่ส่งผลกระทบต่ออุปกรณ์