UndefinedBehaviorSanitizer

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 也有兩個快速鍵 integerdefault-ub,可同時啟用一組清除器。整數會啟用 integer-divide-by-zerosigned-integer-overflowunsigned-integer-overflowdefault-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]'