ควบคุมความสมบูรณ์ของการไหล

ในปี 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 สองตัวอย่าง:

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

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

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

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

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

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

การตรวจสอบความถูกต้อง

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