UndefinedBehaviorSanitizer (UBSan) 會執行編譯時間檢測,檢查各種未定義的行為。雖然 UBSan 能夠偵測許多未定義的行為錯誤,但 Android 支援:
- 對齊
- bool
- 界限
- enum
- float-cast-overflow
- float-divide-by-zero
- integer-divide-by-zero
- nonnull-attribute
- null
- 回傳
- returns-nonnull-attribute
- shift-base
- shift-exponent
- signed-integer-overflow
- 無法連上
- unsigned-integer-overflow
- vla-bound
雖然從技術上來說,無符號整數溢位並非未定義的行為,但仍會納入清除器,並用於許多 Android 模組 (包括 mediaserver 元件),以消除任何潛在的整數溢位安全漏洞。
實作
在 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')
整數溢位清除
非預期的整數溢位可能會導致記憶體毀損,或造成與記憶體存取或記憶體配置相關聯的變數出現資訊揭露漏洞。為解決這個問題,我們在 Android 7.0 中加入了 Clang 的 UndefinedBehaviorSanitizer (UBSan) 簽署和未簽署的整數溢位清除器,以強化媒體架構。在 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 屬性指定 BLOCKLIST 檔案,防止函式和來源檔案經過清理。詳情請參閱「疑難排解」。
如要個別啟用清除器,請使用:
sanitize: {
misc_undefined: ["signed-integer-overflow", "unsigned-integer-overflow"],
diag: {
misc_undefined: ["signed-integer-overflow",
"unsigned-integer-overflow",],
},
BLOCKLIST: "modulename_BLOCKLIST.txt",
},疑難排解
如果您要在新元件中啟用整數溢位清除功能,或是依賴已啟用整數溢位清除功能的平台程式庫,可能會遇到一些問題,導致良性整數溢位造成中止。您應測試已啟用清除功能的元件,確保可以顯示良性溢位。
如要找出因使用者建構中的清除作業而導致的終止,請搜尋
SIGABRT 導致當機的 Abort 訊息,指出 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,而不是指定實際最大值 (範例)
- 迴圈會在條件中遞減無符號整數 (範例、 範例)
建議開發人員先確認溢位案例確實無害,不會產生非預期的副作用或安全性影響,再停用清除作業。
停用 IntSan
您可以使用 BLOCKLIST 或函式屬性停用 IntSan。請盡量不要停用,只有在重構程式碼不合理,或效能負擔過於繁重時才停用。
如要進一步瞭解如何使用函式屬性和封鎖清單檔案格式停用 IntSan,請參閱上游 Clang 說明文件。封鎖清單應以特定清除器為範圍,方法是使用指定目標清除器的區段名稱,以免影響其他清除器。
驗證
目前沒有專門針對整數溢位清除作業的 CTS 測試。 請改為確認 CTS 測試是否通過 (無論是否啟用 IntSan),驗證 IntSan 不會影響裝置。
範圍清除
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 屬性指定 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,請參閱 Clang LLVM 說明文件。使用指定目標清除器的區段名稱,將 BLOCKLIST 範圍限定為特定清除器,以免影響其他清除器。
驗證
CTS 測試中沒有專為 BoundSan 設計的測試。請改為確認 CTS 測試是否通過 (無論是否啟用 BoundSan),以驗證這項功能不會影響裝置。
疑難排解
啟用 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]'