Kiểm soát tính toàn vẹn của luồng

Kể từ năm 2016, khoảng 86% tất cả các lỗ hổng trên Android đều liên quan đến tính an toàn của bộ nhớ. Hầu hết các lỗ hổng đều bị kẻ tấn công khai thác bằng cách thay đổi luồng kiểm soát thông thường của một ứng dụng để thực hiện các hoạt động độc hại tuỳ ý với tất cả các đặc quyền của ứng dụng bị khai thác. Tính toàn vẹn của luồng kiểm soát (CFI) là một cơ chế bảo mật không cho phép thay đổi biểu đồ luồng kiểm soát ban đầu của tệp nhị phân đã biên dịch, khiến việc thực hiện các cuộc tấn công như vậy trở nên khó khăn hơn đáng kể.

Trong Android 8.1, chúng tôi đã bật tính năng triển khai CFI của LLVM trong ngăn xếp nội dung nghe nhìn. Trong Android 9, chúng tôi đã bật CFI trong nhiều thành phần hơn cũng như trong hạt nhân. CFI hệ thống được bật theo mặc định nhưng bạn cần bật CFI nhân.

CFI của LLVM yêu cầu biên dịch bằng Tối ưu hoá thời gian liên kết (LTO). LTO giữ nguyên mã bit LLVM của các tệp đối tượng cho đến thời gian liên kết, cho phép trình biên dịch lý luận tốt hơn về những hoạt động tối ưu hoá có thể thực hiện. Việc bật LTO sẽ làm giảm kích thước của tệp nhị phân cuối cùng và cải thiện hiệu suất, nhưng sẽ làm tăng thời gian biên dịch. Trong quá trình kiểm thử trên Android, việc kết hợp LTO và CFI dẫn đến mức hao tổn không đáng kể đối với kích thước mã và hiệu suất; trong một số trường hợp, cả hai đều được cải thiện.

Để biết thêm thông tin kỹ thuật về CFI và cách xử lý các bước kiểm tra điều khiển chuyển tiếp khác, hãy xem tài liệu thiết kế LLVM.

Ví dụ và nguồn

CFI do trình biên dịch cung cấp và thêm khả năng đo lường vào tệp nhị phân trong thời gian biên dịch. Chúng tôi hỗ trợ CFI trong chuỗi công cụ Clang và hệ thống xây dựng Android trong AOSP.

CFI được bật theo mặc định cho các thiết bị Arm64 cho tập hợp các thành phần trong /platform/build/target/product/cfi-common.mk. Tính năng này cũng được bật trực tiếp trong một tập hợp tệp bản vẽ/tệp makefile của thành phần đa phương tiện, chẳng hạn như /platform/frameworks/av/media/libmedia/Android.bp/platform/frameworks/av/cmds/stagefright/Android.mk.

Triển khai CFI hệ thống

CFI được bật theo mặc định nếu bạn sử dụng Clang và hệ thống xây dựng Android. Vì CFI giúp bảo vệ người dùng Android, nên bạn không nên tắt tính năng này.

Trên thực tế, bạn nên bật CFI cho các thành phần khác. Các ứng cử viên lý tưởng là mã gốc đặc quyền hoặc mã gốc xử lý dữ liệu đầu vào không đáng tin cậy của người dùng. Nếu đang sử dụng clang và hệ thống xây dựng Android, bạn có thể bật CFI trong các thành phần mới bằng cách thêm một vài dòng vào tệp makefile hoặc tệp bản thiết kế.

Hỗ trợ CFI trong tệp makefile

Để bật CFI trong tệp make, chẳng hạn như /platform/frameworks/av/cmds/stagefright/Android.mk, hãy thêm:

LOCAL_SANITIZE := cfi
# Optional features
LOCAL_SANITIZE_DIAG := cfi
LOCAL_SANITIZE_BLACKLIST := cfi_blacklist.txt
  • LOCAL_SANITIZE chỉ định CFI làm trình dọn dẹp trong quá trình tạo bản dựng.
  • LOCAL_SANITIZE_DIAG bật chế độ chẩn đoán cho CFI. Chế độ chẩn đoán sẽ in thêm thông tin gỡ lỗi trong logcat trong các sự cố. Điều này rất hữu ích trong quá trình phát triển và kiểm thử bản dựng. Tuy nhiên, hãy nhớ xoá chế độ chẩn đoán trên các bản dựng chính thức.
  • LOCAL_SANITIZE_BLACKLIST cho phép các thành phần tắt tính năng đo lường CFI cho từng hàm hoặc tệp nguồn. Bạn có thể sử dụng danh sách đen như một giải pháp cuối cùng để khắc phục mọi vấn đề mà người dùng có thể gặp phải. Để biết thêm thông tin chi tiết, hãy xem phần Tắt CFI.

Hỗ trợ CFI trong tệp bản thiết kế

Để bật CFI trong tệp bản thiết kế, chẳng hạn như /platform/frameworks/av/media/libmedia/Android.bp, hãy thêm:

   sanitize: {
        cfi: true,
        diag: {
            cfi: true,
        },
        blacklist: "cfi_blacklist.txt",
    },

Khắc phục sự cố

Nếu đang bật CFI trong các thành phần mới, bạn có thể gặp một số vấn đề về lỗi không khớp loại hàmlỗi không khớp loại mã tập hợp.

Lỗi không khớp loại hàm xảy ra vì CFI hạn chế các lệnh gọi gián tiếp chỉ chuyển đến các hàm có cùng loại động với loại tĩnh được dùng trong lệnh gọi. CFI hạn chế các lệnh gọi hàm thành viên ảo và không ảo chỉ nhảy đến các đối tượng là lớp phái sinh của loại tĩnh của đối tượng dùng để thực hiện lệnh gọi. Điều này có nghĩa là khi bạn có mã vi phạm một trong hai giả định này, hoạt động đo lường mà CFI thêm vào sẽ bị huỷ. Ví dụ: dấu vết ngăn xếp cho thấy một SIGABRT và logcat chứa một dòng về tính toàn vẹn của luồng điều khiển tìm thấy sự không khớp.

Để khắc phục vấn đề này, hãy đảm bảo rằng hàm được gọi có cùng loại được khai báo tĩnh. Sau đây là hai ví dụ về CL:

Một vấn đề khác có thể xảy ra là cố gắng bật CFI trong mã chứa các lệnh gọi gián tiếp đến quá trình tập hợp. Vì mã tập hợp không được nhập, nên điều này dẫn đến việc không khớp loại.

Để khắc phục vấn đề này, hãy tạo trình bao bọc mã gốc cho mỗi lệnh gọi tập hợp và cung cấp cho trình bao bọc cùng một chữ ký hàm với con trỏ gọi. Sau đó, trình bao bọc có thể gọi trực tiếp mã tập hợp. Vì các nhánh trực tiếp không được CFI đo lường (không thể trỏ lại trong thời gian chạy và do đó không gây rủi ro bảo mật), nên việc này sẽ khắc phục vấn đề.

Nếu có quá nhiều hàm tập hợp và không thể khắc phục tất cả các hàm đó, bạn cũng có thể đưa tất cả các hàm chứa lệnh gọi gián tiếp đến tập hợp vào danh sách đen. Bạn không nên làm như vậy vì việc này sẽ tắt tính năng kiểm tra CFI trên các hàm này, do đó mở ra bề mặt tấn công.

Tắt CFI

Chúng tôi không quan sát thấy bất kỳ hao tổn hiệu suất nào, vì vậy, bạn không cần tắt CFI. Tuy nhiên, nếu có tác động đến người dùng, bạn có thể tắt CFI một cách có chọn lọc cho từng hàm hoặc tệp nguồn bằng cách cung cấp tệp danh sách đen trình dọn dẹp tại thời điểm biên dịch. Danh sách đen hướng dẫn trình biên dịch tắt tính năng đo lường CFI ở các vị trí đã chỉ định.

Hệ thống xây dựng Android hỗ trợ danh sách đen theo từng thành phần (cho phép bạn chọn tệp nguồn hoặc các hàm riêng lẻ sẽ không nhận được khả năng đo lường CFI) cho cả Make và Soong. Để biết thêm thông tin chi tiết về định dạng của tệp danh sách đen, hãy xem tài liệu về Clang thượng nguồn.

Xác nhận kết quả

Hiện tại, chưa có quy trình kiểm thử CTS dành riêng cho CFI. Thay vào đó, hãy đảm bảo rằng các bài kiểm thử CTS đều vượt qua dù có bật hay không bật CFI để xác minh rằng CFI không ảnh hưởng đến thiết bị.