UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer (UBSan) thực hiện hoạt động đo lường thời gian biên dịch để kiểm tra nhiều loại hành vi không xác định. Mặc dù UBSan có khả năng phát hiện nhiều lỗi hành vi không xác định, nhưng Android hỗ trợ:

  • căn chỉnh
  • bool
  • ranh giới
  • enum
  • float-cast-overflow
  • float-divide-by-zero
  • integer-divide-by-zero
  • nonnull-attribute
  • null
  • câu lệnh trả về
  • returns-nonnull-attribute
  • shift-base
  • shift-exponent
  • signed-integer-overflow
  • không truy cập được
  • unsigned-integer-overflow
  • vla-bound

unsigned-integer-overflow (tràn số nguyên không dấu), mặc dù không phải là hành vi không xác định về mặt kỹ thuật, nhưng được đưa vào trình dọn dẹp và được dùng trong nhiều mô-đun Android, bao gồm cả các thành phần mediaserver, để loại bỏ mọi lỗ hổng tràn số nguyên tiềm ẩn.

Triển khai

Trong hệ thống bản dựng Android, bạn có thể bật UBSan trên toàn cục hoặc cục bộ. Để bật UBSan trên toàn cục, hãy đặt SANITIZE_TARGET trong Android.mk. Để bật UBSan ở cấp độ mỗi mô-đun, hãy đặt LOCAL_SANITIZE và chỉ định các hành vi không xác định mà bạn muốn tìm trong Android.mk. Ví dụ:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)

LOCAL_CFLAGS := -std=c11 -Wall -Werror -O0

LOCAL_SRC_FILES:= sanitizer-status.c

LOCAL_MODULE:= sanitizer-status

LOCAL_SANITIZE := alignment bounds null unreachable integer
LOCAL_SANITIZE_DIAG := alignment bounds null unreachable integer

include $(BUILD_EXECUTABLE)

Và cấu hình bản thiết kế (Android.bp) tương đương:

cc_binary {

    cflags: [
        "-std=c11",
        "-Wall",
        "-Werror",
        "-O0",
    ],

    srcs: ["sanitizer-status.c"],

    name: "sanitizer-status",

    sanitize: {
        misc_undefined: [
            "alignment",
            "bounds",
            "null",
            "unreachable",
            "integer",
        ],
        diag: {
            misc_undefined: [
                "alignment",
                "bounds",
                "null",
                "unreachable",
                "integer",
            ],
        },
    },

}

Lối tắt UBSan

Android cũng có 2 lối tắt là integerdefault-ub để bật một nhóm trình dọn dẹp cùng một lúc. số nguyên cho phép integer-divide-by-zero, signed-integer-overflowunsigned-integer-overflow. default-ub cho phép các bước kiểm tra có vấn đề tối thiểu về hiệu suất trình biên dịch: bool, integer-divide-by-zero, return, returns-nonnull-attribute, shift-exponent, unreachable and vla-bound. Bạn có thể dùng lớp trình dọn dẹp số nguyên với SANITIZE_TARGET và LOCAL_SANITIZE, trong khi default-ub chỉ có thể dùng với SANITIZE_TARGET.

Báo cáo lỗi chính xác hơn

Hoạt động triển khai UBSan mặc định của Android sẽ gọi một hàm được chỉ định khi gặp phải hành vi không xác định. Theo mặc định, hàm này sẽ huỷ. Tuy nhiên, kể từ tháng 10 năm 2016, UBSan trên Android có một thư viện thời gian chạy không bắt buộc, cung cấp báo cáo lỗi chi tiết hơn, bao gồm cả loại hành vi không xác định gặp phải, thông tin về tệp và dòng mã nguồn. Để bật tính năng báo cáo lỗi này bằng các quy trình kiểm tra số nguyên, hãy thêm nội dung sau vào tệp Android.mk:

LOCAL_SANITIZE:=integer
LOCAL_SANITIZE_DIAG:=integer

Giá trị LOCAL_SANITIZE cho phép trình dọn dẹp trong quá trình tạo. LOCAL_SANITIZE_DIAG bật chế độ chẩn đoán cho trình dọn dẹp được chỉ định. Bạn có thể đặt LOCAL_SANITIZE và LOCAL_SANITIZE_DIAG thành các giá trị khác nhau, nhưng chỉ những chế độ kiểm tra trong LOCAL_SANITIZE mới được bật. Nếu một quy trình kiểm tra không được chỉ định trong LOCAL_SANITIZE nhưng được chỉ định trong LOCAL_SANITIZE_DIAG, thì quy trình kiểm tra đó sẽ không được bật và thông báo chẩn đoán sẽ không được đưa ra.

Sau đây là ví dụ về thông tin do thư viện thời gian chạy UBSan cung cấp:

pixel-xl:/ # sanitizer-status ubsan
sanitizer-status/sanitizer-status.c:53:6: runtime error: unsigned integer overflow: 18446744073709551615 + 1 cannot be represented in type 'size_t' (aka 'unsigned long')

Vệ sinh tràn số nguyên

Tình trạng tràn số nguyên ngoài ý muốn có thể gây ra lỗi hỏng bộ nhớ hoặc lỗ hổng bảo mật tiết lộ thông tin trong các biến được liên kết với hoạt động truy cập bộ nhớ hoặc việc phân bổ bộ nhớ. Để khắc phục vấn đề này, chúng tôi đã thêm các trình dọn dẹp số nguyên có dấu và không dấu UndefinedBehaviorSanitizer (UBSan) của Clang để tăng cường bảo mật cho khung đa phương tiện trong Android 7.0. Trong Android 9, chúng tôi đã mở rộng UBSan để bao gồm nhiều thành phần hơn và cải thiện khả năng hỗ trợ hệ thống bản dựng cho UBSan.

Điều này được thiết kế để thêm các bước kiểm tra xung quanh các phép toán số học/hướng dẫn (có thể tràn) để huỷ một quy trình một cách an toàn nếu xảy ra tình trạng tràn. Những trình dọn dẹp này có thể giảm thiểu toàn bộ lớp lỗ hổng bảo mật làm hỏng bộ nhớ và tiết lộ thông tin, trong đó nguyên nhân gốc là lỗi tràn số nguyên, chẳng hạn như lỗ hổng bảo mật Stagefright ban đầu.

Ví dụ và nguồn

Tính năng dọn dẹp tràn số nguyên (IntSan) do trình biên dịch cung cấp và thêm tính năng đo lường vào tệp nhị phân trong thời gian biên dịch để phát hiện các lỗi tràn số học. Tính năng này được bật theo mặc định trong nhiều thành phần trên toàn nền tảng, ví dụ: /platform/external/libnl/Android.bp.

Triển khai

IntSan sử dụng các trình dọn dẹp tràn số nguyên đã ký và chưa ký của UBSan. Biện pháp giảm thiểu này được bật ở cấp độ từng mô-đun. Tính năng này giúp bảo mật các thành phần quan trọng của Android và bạn không nên tắt tính năng này.

Bạn nên bật tính năng Vệ sinh tràn số nguyên cho các thành phần bổ sung. Các ứng cử viên lý tưởng là mã gốc đặc quyền hoặc mã gốc phân tích cú pháp dữ liệu đầu vào không đáng tin cậy của người dùng. Có một mức hao tổn hiệu suất nhỏ liên quan đến trình dọn dẹp, tuỳ thuộc vào mức sử dụng mã và mức độ phổ biến của các phép toán số học. Dự kiến tỷ lệ hao tổn nhỏ và kiểm tra xem hiệu suất có phải là vấn đề hay không.

Hỗ trợ IntSan trong tệp makefile

Để bật IntSan trong tệp makefile, hãy thêm:

LOCAL_SANITIZE := integer_overflow
    # Optional features
    LOCAL_SANITIZE_DIAG := integer_overflow
    LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
  • LOCAL_SANITIZE nhận một danh sách các trình dọn dẹp được phân tách bằng dấu phẩy, trong đó integer_overflow là một tập hợp các lựa chọn được đóng gói sẵn cho các trình dọn dẹp số nguyên có dấu và không dấu riêng lẻ với một BLOCKLIST mặc định.
  • LOCAL_SANITIZE_DIAG bật chế độ chẩn đoán cho các trình dọn dẹp. Chỉ sử dụng chế độ chẩn đoán trong quá trình kiểm thử vì chế độ này sẽ không huỷ bỏ khi xảy ra tràn, hoàn toàn phủ nhận lợi thế bảo mật của biện pháp giảm thiểu. Hãy xem phần Khắc phục sự cố để biết thêm thông tin chi tiết.
  • LOCAL_SANITIZE_BLOCKLIST cho phép bạn chỉ định một tệp BLOCKLIST để ngăn các hàm và tệp nguồn bị dọn dẹp. Hãy xem phần Khắc phục sự cố để biết thêm thông tin chi tiết.

Nếu bạn muốn kiểm soát chi tiết hơn, hãy bật từng trình dọn dẹp bằng cách sử dụng một hoặc cả hai cờ sau:

LOCAL_SANITIZE := signed-integer-overflow, unsigned-integer-overflow
    LOCAL_SANITIZE_DIAG := signed-integer-overflow, unsigned-integer-overflow

Hỗ trợ IntSan trong các tệp bản thiết kế

Để bật tính năng dọn dẹp tràn số nguyên trong tệp bản thiết kế, chẳng hạn như /platform/external/libnl/Android.bp, hãy thêm:

   sanitize: {
          integer_overflow: true,
          diag: {
              integer_overflow: true,
          },
          BLOCKLIST: "modulename_BLOCKLIST.txt",
       },

Giống như các tệp make, thuộc tính integer_overflow là một tập hợp các lựa chọn được đóng gói sẵn cho từng trình dọn dẹp số nguyên có dấu và không dấu riêng lẻ với một BLOCKLIST mặc định.

Nhóm thuộc tính diag cho phép chế độ chẩn đoán cho các trình dọn dẹp. Chỉ sử dụng chế độ chẩn đoán trong quá trình kiểm thử. Chế độ chẩn đoán không huỷ bỏ khi xảy ra tràn, điều này hoàn toàn phủ nhận lợi thế bảo mật của biện pháp giảm thiểu trong các bản dựng người dùng. Hãy xem phần Khắc phục sự cố để biết thêm thông tin chi tiết.

Thuộc tính BLOCKLIST cho phép chỉ định một tệp BLOCKLIST cho phép nhà phát triển ngăn các hàm và tệp nguồn bị dọn dẹp. Hãy xem phần Khắc phục sự cố để biết thêm thông tin chi tiết.

Để bật từng trình dọn dẹp, hãy sử dụng:

   sanitize: {
          misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"],
          diag: {
              misc_undefined: ["signed-integer-overflow",
                               "unsigned-integer-overflow",],
          },
          BLOCKLIST: "modulename_BLOCKLIST.txt",
       },

Khắc phục sự cố

Nếu đang bật tính năng dọn dẹp tràn số nguyên trong các thành phần mới hoặc dựa vào các thư viện nền tảng đã có tính năng dọn dẹp tràn số nguyên, thì bạn có thể gặp phải một số vấn đề với tình trạng tràn số nguyên lành tính gây ra các hoạt động huỷ bỏ. Bạn nên kiểm thử các thành phần có bật tính năng dọn dẹp để đảm bảo các tràn lành tính có thể xuất hiện.

Để tìm các lần huỷ do quá trình dọn dẹp trong bản dựng người dùng, hãy tìm SIGABRT sự cố có thông báo Huỷ cho biết tình trạng tràn bộ nhớ bị UBSan phát hiện, chẳng hạn như:

pid: ###, tid: ###, name: Binder:###  >>> /system/bin/surfaceflinger <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'ubsan: sub-overflow'

Dấu vết ngăn xếp phải bao gồm hàm gây ra thao tác huỷ bỏ, tuy nhiên, các thao tác tràn xảy ra trong các hàm nội tuyến có thể không xuất hiện trong dấu vết ngăn xếp.

Để xác định nguyên nhân gốc rễ dễ dàng hơn, hãy bật tính năng chẩn đoán trong thư viện kích hoạt thao tác huỷ và cố gắng tái hiện lỗi. Khi bạn bật chế độ chẩn đoán, quy trình sẽ không bị huỷ mà sẽ tiếp tục chạy. Việc không huỷ bỏ giúp tối đa hoá số lượng tràn lành tính trong một đường dẫn thực thi cụ thể mà không cần phải biên dịch lại sau khi sửa từng lỗi. Chẩn đoán tạo ra một thông báo lỗi bao gồm số dòng và tệp nguồn gây ra lỗi:

frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp:2188:32: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'size_t' (aka 'unsigned long')

Sau khi xác định được phép toán có vấn đề, hãy đảm bảo rằng tình trạng tràn là vô hại và có chủ ý (ví dụ: không ảnh hưởng đến tính bảo mật). Bạn có thể giải quyết vấn đề về việc trình dọn dẹp kết thúc bất thường bằng cách:

  • Tái cấu trúc mã để tránh tràn (ví dụ)
  • Tràn số một cách rõ ràng thông qua các hàm __builtin_*_overflow của Clang (ví dụ)
  • Tắt quy trình dọn dẹp trong hàm bằng cách chỉ định thuộc tính no_sanitize (ví dụ)
  • Tắt quy trình dọn dẹp một hàm hoặc tệp nguồn thông qua tệp BLOCKLIST (ví dụ)

Bạn nên sử dụng giải pháp chi tiết nhất có thể. Ví dụ: một hàm lớn có nhiều phép toán số học và một phép toán tràn duy nhất sẽ phải được tái cấu trúc phép toán duy nhất thay vì toàn bộ hàm bị đưa vào DANH SÁCH CHẶN.

Sau đây là một số mẫu phổ biến có thể dẫn đến tình trạng tràn bộ nhớ không gây hại:

  • Truyền ngầm khi xảy ra tràn số không dấu trước khi được truyền đến một loại có dấu (ví dụ)
  • Xoá danh sách được liên kết làm giảm chỉ mục vòng lặp khi xoá (ví dụ)
  • Chỉ định một loại không có dấu cho -1 thay vì chỉ định giá trị tối đa thực tế (ví dụ)
  • Vòng lặp giảm số nguyên không dấu trong điều kiện (ví dụ, ví dụ)

Nhà phát triển nên đảm bảo rằng các trường hợp mà trình dọn dẹp phát hiện thấy một lỗi tràn thực sự là lỗi lành tính, không có tác dụng phụ ngoài ý muốn hoặc ảnh hưởng đến bảo mật trước khi tắt tính năng dọn dẹp.

Tắt IntSan

Bạn có thể tắt IntSan bằng BLOCKLIST hoặc thuộc tính hàm. Chỉ tắt khi cần thiết và chỉ khi việc tái cấu trúc mã là không hợp lý hoặc nếu có chi phí hiệu suất có vấn đề.

Hãy xem tài liệu Clang nguồn gốc để biết thêm thông tin về cách tắt IntSan bằng thuộc tính hàmđịnh dạng tệp BLOCKLIST. Bạn nên giới hạn BLOCKLISTing trong trình dọn dẹp cụ thể bằng cách sử dụng tên phần chỉ định trình dọn dẹp mục tiêu để tránh ảnh hưởng đến các trình dọn dẹp khác.

Xác nhận kết quả

Hiện tại, không có kiểm thử CTS nào dành riêng cho tính năng Vệ sinh tràn số nguyên. Thay vào đó, hãy đảm bảo rằng các kiểm thử CTS đều vượt qua khi bật hoặc không bật IntSan để xác minh rằng IntSan không ảnh hưởng đến thiết bị.

Dọn dẹp ranh giới

BoundsSanitizer (BoundSan) thêm tính năng đo lường vào các tệp nhị phân để chèn các chế độ kiểm tra ranh giới xung quanh các lần truy cập mảng. Các quy trình kiểm tra này được thêm vào nếu trình biên dịch không thể chứng minh tại thời điểm biên dịch rằng quyền truy cập sẽ an toàn và nếu kích thước của mảng sẽ được biết tại thời gian chạy, thì có thể kiểm tra theo kích thước đó. Android 10 triển khai BoundSan trong Bluetooth và các bộ mã hoá và giải mã. BoundSan do trình biên dịch cung cấp và được bật theo mặc định trong nhiều thành phần trên toàn nền tảng.

Triển khai

BoundSan sử dụng trình dọn dẹp ranh giới của UBSan. Biện pháp giảm thiểu này được bật ở cấp độ từng mô-đun. Tính năng này giúp bảo mật các thành phần quan trọng của Android và bạn không nên tắt tính năng này.

Bạn nên bật BoundSan cho các thành phần khác. Các ứng cử viên lý tưởng là mã gốc có đặc quyền hoặc mã gốc phức tạp phân tích cú pháp dữ liệu đầu vào không đáng tin cậy của người dùng. Mức hao tổn hiệu suất liên quan đến việc bật BoundSan phụ thuộc vào số lượng lượt truy cập mảng không thể chứng minh là an toàn. Thông thường, tỷ lệ hao tổn sẽ nhỏ và bạn nên kiểm thử nếu hiệu suất là vấn đề đáng lo ngại.

Bật BoundSan trong các tệp bản thiết kế

Bạn có thể bật BoundSan trong các tệp bản thiết kế bằng cách thêm "bounds" vào thuộc tính misc_undefined sanitize cho các mô-đun nhị phân và thư viện:

    sanitize: {
       misc_undefined: ["bounds"],
       diag: {
          misc_undefined: ["bounds"],
       },
       BLOCKLIST: "modulename_BLOCKLIST.txt",
diag

Thuộc tính diag cho phép chế độ chẩn đoán cho các trình dọn dẹp. Chỉ sử dụng chế độ chẩn đoán trong quá trình kiểm thử. Chế độ chẩn đoán không huỷ bỏ khi xảy ra tình trạng tràn, điều này làm mất đi lợi thế bảo mật của biện pháp giảm thiểu và làm tăng mức hao tổn hiệu suất, vì vậy, bạn không nên dùng chế độ này cho các bản dựng phát hành công khai.

DANH SÁCH CHẶN

Thuộc tính BLOCKLIST cho phép chỉ định một tệp BLOCKLIST mà nhà phát triển có thể dùng để ngăn các hàm và tệp nguồn bị dọn dẹp. Chỉ sử dụng thuộc tính này nếu hiệu suất là một vấn đề và các tệp/hàm được nhắm đến đóng góp đáng kể. Kiểm tra các tệp/hàm này theo cách thủ công để đảm bảo rằng các hoạt động truy cập mảng đều an toàn. Hãy xem phần Khắc phục sự cố để biết thêm thông tin chi tiết.

Bật BoundSan trong tệp makefile

Bạn có thể bật BoundSan trong tệp makefile bằng cách thêm "bounds" vào biến LOCAL_SANITIZE cho các mô-đun nhị phân và thư viện:

    LOCAL_SANITIZE := bounds
    # Optional features
    LOCAL_SANITIZE_DIAG := bounds
    LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt

LOCAL_SANITIZE chấp nhận danh sách các trình dọn dẹp được phân tách bằng dấu phẩy.

LOCAL_SANITIZE_DIAG bật chế độ chẩn đoán. Chỉ sử dụng chế độ chẩn đoán trong quá trình kiểm thử. Chế độ chẩn đoán không huỷ bỏ khi xảy ra tràn, điều này phủ nhận lợi thế bảo mật của biện pháp giảm thiểu và làm tăng mức hao tổn hiệu suất. Vì vậy, bạn không nên dùng chế độ này cho các bản dựng phát hành công khai.

LOCAL_SANITIZE_BLOCKLIST cho phép chỉ định một tệp BLOCKLIST cho phép nhà phát triển ngăn các hàm và tệp nguồn bị dọn dẹp. Chỉ sử dụng thuộc tính này nếu hiệu suất là một vấn đề và các tệp/hàm được nhắm đến đóng góp đáng kể. Kiểm tra các tệp/hàm này theo cách thủ công để đảm bảo rằng các hoạt động truy cập mảng đều an toàn. Hãy xem phần Khắc phục sự cố để biết thêm thông tin chi tiết.

Tắt BoundSan

Bạn có thể tắt BoundSan trong các hàm và tệp nguồn bằng BLOCKLIST hoặc thuộc tính hàm. Tốt nhất là bạn nên bật BoundSan, vì vậy, chỉ tắt BoundSan nếu hàm hoặc tệp đang tạo ra một lượng lớn hiệu suất và nguồn đã được xem xét theo cách thủ công.

Để biết thêm thông tin về cách tắt BoundSan bằng thuộc tính hàmđịnh dạng tệp BLOCKLIST, hãy tham khảo tài liệu Clang LLVM. Phạm vi BLOCKLISTing cho trình dọn dẹp cụ thể bằng cách sử dụng tên phần chỉ định trình dọn dẹp mục tiêu để tránh ảnh hưởng đến các trình dọn dẹp khác.

Xác nhận kết quả

Không có bài kiểm thử CTS nào dành riêng cho BoundSan. Thay vào đó, hãy đảm bảo rằng các kiểm thử CTS đều vượt qua khi có hoặc không có BoundSan được bật để xác minh rằng BoundSan không ảnh hưởng đến thiết bị.

Khắc phục sự cố

Kiểm thử kỹ lưỡng các thành phần sau khi bật BoundSan để đảm bảo rằng mọi hoạt động truy cập ngoài phạm vi chưa được phát hiện trước đó đều được giải quyết.

Bạn có thể dễ dàng xác định lỗi BoundSan vì lỗi này bao gồm thông báo kết thúc sau đây:

    pid: ###, tid: ###, name: Binder:###  >>> /system/bin/foobar <<<
    signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
    Abort message: 'ubsan: out-of-bounds'

Khi chạy ở chế độ chẩn đoán, tệp nguồn, số dòng và giá trị chỉ mục sẽ được in vào logcat. Theo mặc định, chế độ này không gửi thông báo huỷ. Xem logcat để kiểm tra xem có lỗi không.

    external/foo/bar.c:293:13: runtime error: index -1 out of bounds for type 'int [24]'