如何利用Sanitizer解決Android中的Bug?

谷歌開發者_發表於2017-08-15

文 / Google Android 安全團隊 Dan Austin


LLVM 是構建 Android 所使用的編譯器基礎架構,包含多個用於執行靜態和動態分析的元件。在分析 Android 時,得到廣泛使用的一組元件是擦除器,特別是 AddressSanitizer、UndefinedBehaviorSanitizer 和 SanitizerCoverage。


這些擦除器是包含在 compiler-rt 中的基於編譯器的儀器測試元件,可在開發和測試過程中用於消除錯誤和優化 Android。Android 中當前提供的這些擦除器可以發現和診斷多種記憶體濫用錯誤和未定義的行為,還可以提供程式碼覆蓋指標,確保您的測試套件儘可能面面俱到。

本文將詳細介紹現有 Android 擦除器(AddressSanitizer、UndefinedBehaviorSanitizer 和 SanitizerCoverage)的內部結構,並演示可以如何在 Android 構建系統中使用它們。



Address Sanitizer

AddressSanitizer (ASan) 是一項基於編譯器的儀器測試功能,可在執行時檢測 C/C++ 程式碼中的多種記憶體錯誤。在 Android 中,已經測試了對下列記憶體錯誤型別的檢查功能:

  • 堆、堆疊和全域性變數的越界訪問

  • 釋放後使用

  • 返回後使用(執行時標誌 ASAN_OPTIONS=detect_stack_use_after_return=1)

  • 範圍後使用(clang 標誌 -fsanitize-address-use-after-scope)

  • 重複釋放,無效釋放


Android 可通過 ASan 執行全面的構建儀器測試,還可以通過 asanwrapper 執行應用級的 ASan 儀器測試。關於這兩種儀器測試技巧的說明均可在 source.android.com 中找到。

AddressSanitizer 基於以下兩個高階概念。第一個概念是針對與記憶體有關的所有函式呼叫(包括 alloca、malloc 和 free 等)執行儀器測試並輸出用於跟蹤記憶體分配、釋放和使用情況統計的資訊。通過此儀器測試,ASan 可檢測無效的記憶體使用錯誤,包括重複釋放、範圍後使用、返回後使用和釋放後使用等錯誤。ASan 還可以檢測在定義的記憶體區域邊界外發生的讀寫操作。為完成此檢測,它填充所有分配的記憶體緩衝區和變數。如果對此填充區域進行讀或寫,ASan 將捕獲此操作,並輸出有助於診斷記憶體違例的資訊。在 ASan 術語中,此填充被稱為中毒記憶體。


下面是包含堆疊分配變數的中毒記憶體填充佈局示例:

640?wx_fmt=png


▲ ASANified 堆疊變數示例,此變數包含一個由 8 個元素組成的 int8_t 陣列、一個 uint32_t 陣列和一個由 16 個元素組成的 int8_t 陣列。右側顯示使用 ASAN 編譯後的記憶體佈局,其中每個變數之間插入填充。對於每個堆疊變數,變數前後有 32 個填充位元組。如果一個變數的物件大小不是 32 個位元組,則插入 32 - n 個額外的填充位元組,其中 n 是物件大小。


ASan 使用影子記憶體跟蹤哪些位元組為正常記憶體,哪些位元組為中毒記憶體。位元組可以標記為完全正常(在影子記憶體中標記為 0)、完全中毒(設定對應影子位元組的高位)或前面 k 個位元組未中毒(影子位元組值為 k)。如果影子記憶體顯示某個位元組中毒,則 ASan 會使程式崩潰,並輸出有用的除錯資訊,包括呼叫堆疊、影子記憶體對映、記憶體違例型別、讀取或寫入的內容、導致違例的計算機以及記憶體內容。

AddressSanitizer: heap-buffer-overflow on address 0xe6146cf3 at pc 0xe86eeb3c bp 0xffe67348 sp 0xffe66f14 WRITE of size 39 at 0xe6146cf3 thread T0 #0 0xe86eeb3b (/system/lib/libclang_rt.asan-arm-android.so+0x64b3b) #1 0xaddc5d27 (/data/simple_test_fuzzer+0x4d27) #2 0xaddd08b9 (/data/simple_test_fuzzer+0xf8b9) #3 0xaddd0a97 (/data/simple_test_fuzzer+0xfa97) #4 0xaddd0fbb (/data/simple_test_fuzzer+0xffbb) #5 0xaddd109f (/data/simple_test_fuzzer+0x1009f) #6 0xaddcbfb9 (/data/simple_test_fuzzer+0xafb9) #7 0xaddc9ceb (/data/simple_test_fuzzer+0x8ceb) #8 0xe8655635 (/system/lib/libc.so+0x7a635) 0xe6146cf3 is located 0 bytes to the right of 35-byte region [0xe6146cd0,0xe6146cf3) allocated by thread T0 here: #0 0xe87159df (/system/lib/libclang_rt.asan-arm-android.so+0x8b9df) #1 0xaddc5ca7 (/data/simple_test_fuzzer+0x4ca7) #2 0xaddd08b9 (/data/simple_test_fuzzer+0xf8b9) SUMMARY: AddressSanitizer: heap-buffer-overflow (/system/lib/libclang_rt.asan-arm-android.so+0x64b3b) Shadow bytes around the buggy address: 0x1cc28d40: fa fa 00 00 00 00 07 fa fa fa fd fd fd fd fd fd 0x1cc28d50: fa fa 00 00 00 00 07 fa fa fa fd fd fd fd fd fd 0x1cc28d60: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd 0x1cc28d70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x1cc28d80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x1cc28d90: fa fa fa fa fa fa fa fa fa fa 00 00 00 00[03]fa 0x1cc28da0: fa fa 00 00 00 00 07 fa fa fa 00 00 00 00 03 fa 0x1cc28db0: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa 0x1cc28dc0: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd 0x1cc28dd0: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd 0x1cc28de0: fa fa 00 00 00 00 00 02 fa fa fd fd fd fd fd fd Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb


有關報告各個部分的含義以及如何提高其易讀性的更多資訊,可檢視 LLVM 網站:

https://clang.llvm.org/docs/AddressSanitizer.html


和 Github:

https://github.com/google/sanitizers/wiki/AddressSanitizer


有時,錯誤發現過程可能無法確定問題所在,當錯誤需要特殊設定或更高階的技巧(例如堆填充或利用爭用條件)才能發現時,更是如此。其中許多錯誤並不能即時發現,可能需要檢查數千條指令才能找到記憶體違例的真正原因所在。ASan 可針對所有與記憶體有關的函式執行儀器測試併為必須觸發 ASan 相關回撥才可訪問的區域填充資料,可在發生記憶體違例時立即捕獲違例,而不是等待崩潰導致資料損壞。這對於錯誤發現和根源診斷極為有用。此外,ASAN 還是一個非常有用的模糊測試工具,一直用於 Android 上的各種模糊測試工作。



UBSan

UndefinedBehaviorSanitizer (UBSan) 執行編譯時儀器測試,檢查各種未定義的行為。裝置製造商可通過將 LOCAL_SANITIZE:=default-ub 包含到生成檔案或將 default-ub: true 包含到 blueprint 檔案的 sanitize 塊中,將 UBSan 加入其測試構建中。UBSan 可以檢測多種未定義的行為,而 Android 的構建系統直接支援:

  • bool

  • integer-divide-by-zero

  • return

  • returns-nonnull-attribute

  • shift-exponent

  • unreachable

  • vla-bound


Android 的構建系統還使用了 UBSan 的整數溢位檢查功能。UBSan 還支援 unsigned-integer-overflow,這不是嚴格意義上的未定義行為,但它包含在擦除器中。在生成檔案中,可以將 LOCAL_SANITIZE 設定為 signed-integer-overflow、unsigned-integer-overflow 或 combination flag integer,啟用 signed-integer-overflow、unsigned-integer-overflow、integer-divide-by-zero、shift-base 和 shift-exponent,以啟用這些行為。在 blueprint 檔案中,可以將 Misc_undefined 設定為所需的標誌,啟用這些行為。這些 UBSan 目標,尤其是 unsigned-integer-overflow,廣泛用於 mediaserver 元件中,以用來消除任何潛在的整數溢位漏洞。

在 Android 中,當出現未定義的行為時,預設的做法是中止程式。但是,從 2016 年 10 月開始,Android 中的 UBSan 將提供一個可選的執行時庫,其報告的錯誤資訊將更加詳細,包括出現的未定義行為型別、檔案和原始碼行資訊。

在 Android.mk 檔案中,可通過以下方式啟用該庫:

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


而在 Android.bp 檔案中,可通過以下方式啟用該庫:

sanitize: { misc_undefined: [ "unsigned-integer-overflow", "signed-integer-overflow", ], diag: { misc_undefined: [ "unsigned-integer-overflow", "signed-integer-overflow", ], }, },


下面是 UBSan 執行時庫提供的資訊示例:

external/icu/icu4c/source/common/ucnv.c:1193:23: runtime error: unsigned integer overflow: 4291925010 + 2147483647 cannot be represented in type 'unsigned int' external/icu/icu4c/source/common/cstring.c:288:16: runtime error: unsigned integer overflow: 0 - 1 cannot be represented in type 'uint32_t' (aka 'unsigned int') external/harfbuzz_ng/src/hb-private.hh:894:16: runtime error: unsigned integer overflow: 72 - 55296 cannot be represented in type 'unsigned int' external/harfbuzz_ng/src/hb-set-private.hh:82:24: runtime error: unsigned integer overflow: 32 - 562949953421312 cannot be represented in type 'unsigned long' system/keymaster/authorization_set.cpp:500:37: runtime error: unsigned integer overflow: 6843601868186924302 * 24 cannot be represented in type 'unsigned long'



SanitizerCoverage

Sanitizer 工具內建一個非常簡單的程式碼覆蓋工具。SanitizerCoverage 可實現呼叫級、基本塊級和邊緣級的程式碼覆蓋。此 Sanitizer 可用作獨立的儀器測試工具,也可與其他任何擦除器配合使用,包括 AddressSanitizer 和 UndefinedBehaviorSanitizer 等。要使用這一基於 Guard 的新覆蓋工具,請設定 fsanitize-coverage=trace-pc-guard。編譯器將在每個邊緣插入 __sanitizer_cov_trace_pc_guard(&guard_variable)。每個邊緣均有各自的 uint32_t guard_variable。


此外,還會生成模組建構函式,即 __sanitizer_cov_trace_pc_guard_init(uint32_t* start, uint32_t* stop)。所有 __sanitizer_cov_ 函式均應由使用者提供。您可以按照使用 Guard 跟蹤計算機中的示例操作:

https://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards


除了跟蹤控制流外,SanitizerCoverage 還可以跟蹤資料流。此功能可通過 fsanitize-coverage=trace-cmp 啟用,並通過 __sanitizer_cov_trace_* 函式檢測所有開關和比較指令來實現。對於整數除法和 GEP 指令,也存在類似的功能,可分別通過 fsanitize-coverage=trace-div 和 fsanitize-coverage=trace-gep 啟用。這是一個實驗性介面,可能危及執行緒安全,可能隨時更改,但在 Android 構建中提供並可執行此介面。

在擦除器覆蓋會話期間,覆蓋資訊記錄在兩個檔案中,即 .sancov 檔案和 sancov.map 檔案。前一個檔案包含程式的所有儀器測試點,而後一個檔案包含在前一個檔案中用一系列索引表示的執行跟蹤。預設情況下,這兩個檔案儲存在當前工作目錄中,系統將為執行過程中執行的每個可執行的共享物件建立一個目錄。



結論

ASan、UBSan 和 SanitizerCoverage 僅僅是 LLVM 擦除器在 Android 中應用的開端。目前,正在向 Android 構建系統中整合更多的 LLVM 擦除器。本文所介紹的擦除器可用作程式碼執行狀況和系統穩定性檢測工具,Android 安全團隊甚至正在用它們來發現和預防安全錯誤!


檢視全文及文中連結,請點選文末“閱讀原文”。


推薦閱讀:

Android O中的Seccomp過濾器

Android O中關於快捷方式和微件的更新

宣佈推出Nearby Connections 2.0

Android測試支援庫1.0現已釋出!


640?wx_fmt=gif

相關文章