UndefinedBehaviorSanitizer (UBSan) 會執行編譯時檢測作業,檢查各種未定義行為。雖然 UBSan 可偵測許多未定義行為錯誤,但 Android 支援以下功能:
- 對齊
- 布林值
- 範圍
- 列舉
- float-cast-overflow
- float-divide-by-zero
- integer-divide-by-zero
- 非空值屬性
- null
- 回傳
- returns-nonnull-attribute
- shift-base
- shift-exponent
- signed-integer-overflow
- 無法連上
- unsigned-integer-overflow
- vla-bound
無符號整數溢位雖然在技術上並未定義行為,但已納入消毒器,並用於許多 Android 模組 (包括媒體伺服器元件),以消除任何潛在的整數溢位安全漏洞。
實作
在 Android 建構系統中,您可以在全域或本機啟用 UBSan。如要全域啟用 UBSan,請在 Android.mk 中設定 SANITIZE_TARGET。如要在各模組層級啟用 UBSan,請設定 LOCAL_SANITIZE,並指定您要在 Android.mk 中尋找的未定義行為。例如:
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)
以及等效的藍圖 (Android.bp) 設定:
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", ], }, }, }
UBSan 捷徑
Android 也提供兩個捷徑 integer
和 default-ub
,可同時啟用一組消毒工具。整數可啟用 integer-divide-by-zero
、signed-integer-overflow
和 unsigned-integer-overflow
。default-ub
可啟用檢查,以便減少編譯器效能問題:bool, integer-divide-by-zero, return,
returns-nonnull-attribute, shift-exponent, unreachable and vla-bound
。整數淨化器類別可搭配 SANITIZE_TARGET 和 LOCAL_SANITIZE 使用,而 default-ub 只能搭配 SANITIZE_TARGET 使用。
改善錯誤回報
當遇到未定義行為時,Android 的預設 UBSan 實作會叫用指定的函式。根據預設,這個函式會中止。不過,自 2016 年 10 月起,Android 上的 UBSan 便提供可選的執行階段程式庫,可提供更詳細的錯誤回報,包括遇到的未定義行為類型、檔案和原始碼行資訊。如要啟用此錯誤回報功能,並使用整數檢查,請在 Android.mk 檔案中加入以下內容:
LOCAL_SANITIZE:=integer LOCAL_SANITIZE_DIAG:=integer
LOCAL_SANITIZE 值會在建構期間啟用消毒工具。LOCAL_SANITIZE_DIAG 會為指定的消毒程式啟用診斷模式。您可以將 LOCAL_SANITIZE 和 LOCAL_SANITIZE_DIAG 設為不同的值,但只有 LOCAL_SANITIZE 中的檢查項目會啟用。如果 LOCAL_SANITIZE 中未指定檢查項目,但 LOCAL_SANITIZE_DIAG 中指定了檢查項目,則系統不會啟用檢查項目,也不會提供診斷訊息。
以下是 UBSan 執行階段程式庫提供的資訊範例:
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')
整數溢位清理
非預期的整數溢位可能會導致記憶體毀損,或在與記憶體存取或記憶體配置相關聯的變數中出現資訊揭露漏洞。為解決這個問題,我們新增了 Clang 的 UndefinedBehaviorSanitizer (UBSan) 簽章和未簽章整數溢位清理工具,以便在 Android 7.0 中加強媒體架構。在 Android 9 中,我們擴充了 UBSan 的涵蓋範圍,以涵蓋更多元件,並改善了對 UBSan 的建構系統支援。
這項功能旨在針對可能發生溢位情形的算術運算/指令,加入檢查機制,以便在發生溢位時安全地中止程序。這些清理工具可減輕整個記憶體毀損類別和資訊洩漏安全漏洞的風險,只要這些安全漏洞的根本原因是整數溢位,例如原始的 Stagefright 安全漏洞。
範例和來源
整數溢位清理 (IntSan) 是由編譯器提供,會在編譯期間將檢測器加入二進位檔,以便偵測算術溢位。根據預設,系統會在整個平台的各種元件中啟用這項功能,例如 /platform/external/libnl/Android.bp
。
實作
IntSan 會使用 UBSan 的帶正負號和未帶正負號整數溢位清理工具。這項緩解措施會在每個模組層級啟用。這有助於確保 Android 的重要元件安全無虞,因此不應停用。
我們強烈建議您為其他元件啟用整數溢位清理功能。理想的候選項目是具有特權的原生程式碼,或是剖析不受信任的使用者輸入內容的原生程式碼。消毒工具會產生少許效能負載,這取決於程式碼的用途和算術運算的普遍性。預期會有少許的額外百分比,並測試是否有效能問題。
在 makefile 中支援 IntSan
如要在 makefile 中啟用 IntSan,請新增:
LOCAL_SANITIZE := integer_overflow # Optional features LOCAL_SANITIZE_DIAG := integer_overflow LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
LOCAL_SANITIZE
會接收以半形逗號分隔的清單,其中integer_overflow
是預先封裝的選項集,可為個別已簽署和未簽署整數溢位清理工具提供預設 BLOCKLIST。LOCAL_SANITIZE_DIAG
會為消毒工具啟用診斷模式。請僅在測試期間使用診斷模式,因為這不會在溢位時中止,完全抵銷緩解措施的安全性優勢。詳情請參閱疑難排解。LOCAL_SANITIZE_BLOCKLIST
可讓您指定 BLOCKLIST 檔案,以免函式和來源檔案遭到清理。詳情請參閱疑難排解。
如需更精細的控制,請使用下列其中一個或兩個標記來個別啟用消毒工具:
LOCAL_SANITIZE := signed-integer-overflow, unsigned-integer-overflow LOCAL_SANITIZE_DIAG := signed-integer-overflow, unsigned-integer-overflow
支援藍圖檔案中的 IntSan
如要在藍圖檔案 (例如 /platform/external/libnl/Android.bp
) 中啟用整數溢位清理功能,請新增以下項目:
sanitize: { integer_overflow: true, diag: { integer_overflow: true, }, BLOCKLIST: "modulename_BLOCKLIST.txt", },
與 Make 檔案一樣,integer_overflow
屬性是預先封裝的選項集,可用於個別已簽署和未簽署的整數溢位清理器,並設有預設的 BLOCKLIST。
diag
屬性組合可為消毒工具啟用診斷模式。請僅在測試期間使用診斷模式。診斷模式不會在溢位時中止,這會完全抵銷使用者版本中緩解措施的安全優勢。詳情請參閱「疑難排解」。
BLOCKLIST
屬性可讓您指定封鎖清單檔案,方便開發人員防止函式和來源檔案遭到清理。詳情請參閱「疑難排解」。
如要個別啟用消毒工具,請使用:
sanitize: { misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"], diag: { misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow",], }, BLOCKLIST: "modulename_BLOCKLIST.txt", },
疑難排解
如果您在新的元件中啟用整數溢位清理功能,或是依賴已啟用整數溢位清理功能的平台程式庫,可能會遇到幾個問題,其中良性整數溢位會導致中止。您應測試啟用消毒功能的元件,確保能顯示良性溢位。
如要找出使用者版本中因消毒而導致的中止,請搜尋 SIGABRT
當機,並查看「中止」訊息,指出 UBSan 已偵測到溢位,例如:
pid: ###, tid: ###, name: Binder:### >>> /system/bin/surfaceflinger <<< signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- Abort message: 'ubsan: sub-overflow'
堆疊追蹤應包含導致中止的函式,但在內嵌函式中發生的溢位可能不會在堆疊追蹤中顯示。
為更輕鬆地找出根本原因,請在觸發中斷的程式庫中啟用診斷功能,並嘗試重現錯誤。啟用診斷功能後,系統不會中止程序,而是會繼續執行。不中止執行作業有助於在特定執行路徑中盡可能增加良性溢位數量,而無須在修正每個錯誤後重新編譯。診斷程序會產生錯誤訊息,其中包含導致中斷的行號和來源檔案:
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')
找到有問題的算術運算後,請確認溢位是良性且有意義的 (例如沒有安全性影響)。您可以透過下列方式解決消毒工具中止的問題:
- 重構程式碼以避免溢位 (範例)
- 透過 Clang 的 __builtin_*_overflow 函式明確溢位 (示例)
- 指定
no_sanitize
屬性,停用函式中的清理功能 (範例) - 透過 BLOCKLIST 檔案停用函式或來源檔案的清理程序 (範例)
請盡可能使用最精細的解決方案。舉例來說,如果大型函式包含許多算術運算和單一溢位運算,則應將單一運算重構,而非將整個函式加入封鎖清單。
可能導致良性溢出的常見模式包括:
- 隱含轉換:在轉換為帶符號型別之前,發生未經簽署的溢位 (示例)
- 刪除時會遞減迴圈索引的連結清單刪除作業 (範例)
- 將未簽署型別指派給 -1,而非指定實際的最大值 (示例)
- 在條件中遞減未簽署整數的迴圈 (example、example)
建議開發人員在停用消毒功能前,先確認消毒工具偵測到溢位情況確實是良性,且不會產生任何非預期的副作用或安全性影響。
停用 IntSan
您可以使用 BLOCKLIST 或函式屬性停用 IntSan。請謹慎使用,只有在重構程式碼不合理或有問題的效能開銷時,才應停用。
如要進一步瞭解如何使用函式屬性和 BLOCKLIST 檔案格式停用 IntSan,請參閱上游 Clang 說明文件。使用指定目標消毒劑的區段名稱,將 BLOCKLIST 的範圍限制在特定消毒劑,以免影響其他消毒劑。
驗證
目前沒有專門針對整數溢位清理機制的 CTS 測試。請改為確認無論是否啟用 IntSan,CTS 測試都能通過,以驗證這項功能不會影響裝置。
邊界消毒
BoundsSanitizer (BoundSan) 會在二進位檔中加入檢測功能,以便在陣列存取時插入邊界檢查。如果編譯器無法在編譯期間證明存取作業是安全的,且在執行階段可得知陣列的大小,系統就會加入這些檢查,以便進行檢查。Android 10 會在藍牙和編解碼器中部署 BoundSan。BoundSan 是由編譯器提供,並預設在整個平台的各種元件中啟用。
實作
BoundSan 使用 UBSan 的邊界消毒工具。這項緩解措施會在每個模組層級啟用。這有助於確保 Android 的重要元件安全無虞,因此不應停用。
強烈建議您為其他元件啟用 BoundSan。理想的候選項目是具有特權的原生程式碼,或是可剖析不受信任的使用者輸入內容的複雜原生程式碼。啟用 BoundSan 時,效能額外負擔取決於無法證明安全性的陣列存取次數。預期平均會有少許的額外百分比,並測試是否有效能問題。
在藍圖檔案中啟用 BoundSan
如要在藍圖檔案中啟用 BoundSan,請將 "bounds"
新增至二進位檔和程式庫模組的 misc_undefined
消毒屬性:
sanitize: { misc_undefined: ["bounds"], diag: { misc_undefined: ["bounds"], }, BLOCKLIST: "modulename_BLOCKLIST.txt",
diag
diag
屬性會為消毒工具啟用診斷模式。請僅在測試期間使用診斷模式。診斷模式不會在溢位時中止,這會抵銷緩解措施的安全性優勢,並帶來更高的效能負載,因此不建議用於正式版版本。
封鎖清單
BLOCKLIST
屬性可讓開發人員指定封鎖清單檔案,以防止函式和來源檔案遭到清理。只有在成效有疑慮,且指定的檔案/函式有重大影響時,才使用這項屬性。手動稽核這些檔案/函式,確保陣列存取作業安全無虞。詳情請參閱疑難排解。
在 Makefile 中啟用 BoundSan
如要在 Makefile 中啟用 BoundSan,請將 "bounds"
新增至二進位檔和程式庫模組的 LOCAL_SANITIZE
變數:
LOCAL_SANITIZE := bounds # Optional features LOCAL_SANITIZE_DIAG := bounds LOCAL_SANITIZE_BLOCKLIST := modulename_BLOCKLIST.txt
LOCAL_SANITIZE
會接受以半形逗號分隔的消毒劑清單。
LOCAL_SANITIZE_DIAG
會開啟診斷模式。請僅在測試期間使用診斷模式。診斷模式不會在溢位時中止,這會抵銷緩解措施的安全性優勢,並帶來更高的效能負載,因此不建議用於正式版版本。
LOCAL_SANITIZE_BLOCKLIST
可讓您指定 BLOCKLIST 檔案,讓開發人員可防止函式和來源檔案遭到清理。只有在成效有疑慮,且指定的檔案/函式有重大影響時,才使用這項屬性。手動稽核這些檔案/函式,確保陣列存取作業安全無虞。詳情請參閱疑難排解。
停用 BoundSan
您可以使用 BLOCKLIST 或函式屬性,在函式和來源檔案中停用 BoundSan。建議您保持 BoundSan 的啟用狀態,只有在函式或檔案造成大量效能額外負擔,且來源已經過手動審查時,才停用 BoundSan。
如要進一步瞭解如何使用函式屬性和 BLOCKLIST 檔案格式停用 BoundSan,請參閱 Clang LLVM 說明文件。使用指定目標消毒工具的區段名稱,將封鎖清單的範圍限定在特定消毒工具,以免影響其他消毒工具。
驗證
目前沒有專門針對 BoundSan 進行的 CTS 測試。請改為確認無論是否啟用 BoundSan,CTS 測試都能通過,以驗證該功能不會影響裝置。
疑難排解
啟用 BoundSan 後,請徹底測試元件,確保先前未偵測到的任何超出邊界存取行為都已解決。
BoundSan 錯誤很容易辨識,因為它們包含下列墓碑中斷訊息:
pid: ###, tid: ###, name: Binder:### >>> /system/bin/foobar <<< signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr -------- Abort message: 'ubsan: out-of-bounds'
在診斷模式下執行時,來源檔案、行號和索引值會列印到 logcat
。根據預設,這個模式不會擲回中止訊息。查看 logcat
,檢查是否有任何錯誤。
external/foo/bar.c:293:13: runtime error: index -1 out of bounds for type 'int [24]'