如何利用Sanitizer解決Android中的Bug?
LLVM 是構建 Android 所使用的編譯器基礎架構,包含多個用于執(zhí)行靜態(tài)和動態(tài)分析的組件。在分析 Android 時,得到廣泛使用的一組組件是擦除器,特別是 AddressSanitizer、UndefinedBehaviorSanitizer 和 SanitizerCoverage。
這些擦除器是包含在 compiler-rt 中的基于編譯器的儀器測試組件,可在開發(fā)和測試過程中用于消除錯誤和優(yōu)化 Android。Android 中當前提供的這些擦除器可以發(fā)現(xiàn)和診斷多種內存濫用錯誤和未定義的行為,還可以提供代碼覆蓋指標,確保您的測試套件盡可能面面俱到。
本文將詳細介紹現(xiàn)有 Android 擦除器(AddressSanitizer、UndefinedBehaviorSanitizer 和 SanitizerCoverage)的內部結構,并演示可以如何在 Android 構建系統(tǒng)中使用它們。
Address Sanitizer
AddressSanitizer (ASan) 是一項基于編譯器的儀器測試功能,可在運行時檢測 C/C++ 代碼中的多種內存錯誤。在 Android 中,已經測試了對下列內存錯誤類型的檢查功能:
- 堆、堆疊和全局變量的越界訪問
- 釋放后使用
- 返回后使用(運行時標志 ASAN_OPTIONS=detect_stack_use_after_return=1)
- 范圍后使用(clang 標志 -fsanitize-address-use-after-scope)
- 重復釋放,無效釋放
Android 可通過 ASan 執(zhí)行全面的構建儀器測試,還可以通過 asanwrapper 執(zhí)行應用級的 ASan 儀器測試。關于這兩種儀器測試技巧的說明均可在 source.android.com 中找到。
AddressSanitizer 基于以下兩個高級概念。***個概念是針對與內存有關的所有函數調用(包括 alloca、malloc 和 free 等)執(zhí)行儀器測試并輸出用于跟蹤內存分配、釋放和使用情況統(tǒng)計的信息。通過此儀器測試,ASan 可檢測無效的內存使用錯誤,包括重復釋放、范圍后使用、返回后使用和釋放后使用等錯誤。ASan 還可以檢測在定義的內存區(qū)域邊界外發(fā)生的讀寫操作。為完成此檢測,它填充所有分配的內存緩沖區(qū)和變量。如果對此填充區(qū)域進行讀或寫,ASan 將捕獲此操作,并輸出有助于診斷內存違例的信息。在 ASan 術語中,此填充被稱為中毒內存。
下面是包含堆疊分配變量的中毒內存填充布局示例:
上圖是ASANified 堆疊變量示例,此變量包含一個由 8 個元素組成的 int8_t 數組、一個 uint32_t 數組和一個由 16 個元素組成的 int8_t 數組。右側顯示使用 ASAN 編譯后的內存布局,其中每個變量之間插入填充。對于每個堆棧變量,變量前后有 32 個填充字節(jié)。如果一個變量的對象大小不是 32 個字節(jié),則插入 32 - n 個額外的填充字節(jié),其中 n 是對象大小。
ASan 使用影子內存跟蹤哪些字節(jié)為正常內存,哪些字節(jié)為中毒內存。字節(jié)可以標記為完全正常(在影子內存中標記為 0)、完全中毒(設置對應影子字節(jié)的高位)或前面 k 個字節(jié)未中毒(影子字節(jié)值為 k)。如果影子內存顯示某個字節(jié)中毒,則 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
有時,錯誤發(fā)現(xiàn)過程可能無法確定問題所在,當錯誤需要特殊設置或更高級的技巧(例如堆填充或利用爭用條件)才能發(fā)現(xiàn)時,更是如此。其中許多錯誤并不能即時發(fā)現(xiàn),可能需要檢查數千條指令才能找到內存違例的真正原因所在。ASan 可針對所有與內存有關的函數執(zhí)行儀器測試并為必須觸發(fā) ASan 相關回調才可訪問的區(qū)域填充數據,可在發(fā)生內存違例時立即捕獲違例,而不是等待崩潰導致數據損壞。這對于錯誤發(fā)現(xiàn)和根源診斷極為有用。此外,ASAN 還是一個非常有用的模糊測試工具,一直用于 Android 上的各種模糊測試工作。
UBSan
UndefinedBehaviorSanitizer (UBSan) 執(zhí)行編譯時儀器測試,檢查各種未定義的行為。設備制造商可通過將 LOCAL_SANITIZE:=default-ub 包含到生成文件或將 default-ub: true 包含到 blueprint 文件的 sanitize 塊中,將 UBSan 加入其測試構建中。UBSan 可以檢測多種未定義的行為,而 Android 的構建系統(tǒng)直接支持:
- bool
- integer-divide-by-zero
- return
- returns-nonnull-attribute
- shift-exponent
- unreachable
- vla-bound
Android 的構建系統(tǒng)還使用了 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 中,當出現(xiàn)未定義的行為時,默認的做法是中止程序。但是,從 2016 年 10 月開始,Android 中的 UBSan 將提供一個可選的運行時庫,其報告的錯誤信息將更加詳細,包括出現(xiàn)的未定義行為類型、文件和源代碼行信息。
在 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 可實現(xiàn)調用級、基本塊級和邊緣級的代碼覆蓋。此 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_* 函數檢測所有開關和比較指令來實現(xiàn)。對于整數除法和 GEP 指令,也存在類似的功能,可分別通過 fsanitize-coverage=trace-div 和 fsanitize-coverage=trace-gep 激活。這是一個實驗性界面,可能危及線程安全,可能隨時更改,但在 Android 構建中提供并可運行此界面。
在擦除器覆蓋會話期間,覆蓋信息記錄在兩個文件中,即 .sancov 文件和 sancov.map 文件。前一個文件包含程序的所有儀器測試點,而后一個文件包含在前一個文件中用一系列索引表示的執(zhí)行跟蹤。默認情況下,這兩個文件存儲在當前工作目錄中,系統(tǒng)將為執(zhí)行過程中運行的每個可執(zhí)行的共享對象創(chuàng)建一個目錄。
結論
ASan、UBSan 和 SanitizerCoverage 僅僅是 LLVM 擦除器在 Android 中應用的開端。目前,正在向 Android 構建系統(tǒng)中集成更多的 LLVM 擦除器。本文所介紹的擦除器可用作代碼運行狀況和系統(tǒng)穩(wěn)定性檢測工具,Android 安全團隊甚至正在用它們來發(fā)現(xiàn)和預防安全錯誤!
【本文是51CTO專欄機構“谷歌開發(fā)者”的原創(chuàng)稿件,轉載請聯(lián)系原作者(微信公眾號:Google_Developers)】