因爲虛擬機的存在,Android應用開發者們一般不用考慮內存訪問相關的錯誤。而一旦咱們深刻到Native世界中,本來面容和藹的內存便開始兇惡起來。這時,因爲程序員寫法不規範、邏輯疏漏而致使的內存錯誤會通通跳到咱們面前,對咱們嘲諷一番。html
這些錯誤既影響了程序的穩定性,也影響了程序的安全性,由於好多惡意代碼就經過內存錯誤來完成入侵。不過麻煩的是,Native世界中的內存錯誤很難排查,由於不少時候致使問題的地方和發生問題的地方相隔甚遠。爲了更好地解決這些問題,各路大神紛紛祭出本身手中的神器,相互PK,相互補充。linux
ASAN(Address Sanitizer)和HWASAN(Hardware-assisted Address Sanitizer)就是這些工具中的佼佼者。android
在ASAN出來以前,市面上的內存調試工具要麼慢,要麼只能檢測部份內存錯誤,要麼這兩個缺點都有。總之,不夠優秀。c++
HWASAN則是ASAN的升級版,它利用了64位機器上忽略高位地址的特性,將這些被忽略的高位地址從新利用起來,從而大大下降了工具對於CPU和內存帶來的額外負載。git
ASAN工具包含兩大塊:程序員
插樁模塊主要會作兩件事:github
運行時庫也一樣會作兩件事:算法
若是想要了解ASAN的實現原理,那麼shadow memory將是第一個須要瞭解的概念。安全
Shadow memory有一些元數據的思惟在裏面。它雖然也是內存中的一塊區域,可是其中的數據僅僅反應其餘正常內存的狀態信息。因此能夠理解爲正常內存的元數據,而正常內存中存儲的纔是程序真正須要的數據。bash
Malloc函數返回的地址一般是8字節對齊的,所以任意一個由(對齊的)8字節所組成的內存區域必然落在如下9種狀態之中:最前面的k(0≤k≤8)字節是可尋址的,而剩下的8-k字節是不可尋址的。這9種狀態即可以用shadow memory中的一個字節來進行編碼。
實際上,一個byte能夠編碼的狀態總共有256(2^8)種,所以用在這裏綽綽有餘。
Shadow memory和normal memory的映射關係如上圖所示。一個byte的shadow memory反映8個byte normal memory的狀態。那如何根據normal memory的地址找到它對應的shadow memory呢?
對於64位機器上的Android而言,兩者的轉換公式以下:
Shadow memory address = (Normal memory address >> 3) + 0x100000000
右移三位的目的是爲了完成 8➡1的映射,而加一個offset是爲了和Normal memory區分開來。最終內存空間種會存在以下的映射關係:
Bad表明的是shadow memory的shadow memory,所以其中數據沒有意義,該內存區域不可以使用。
上文中提到,8字節組成的memory region共有9中狀態:
爲何0個字節可尋址的狀況shadow memory不爲0,而是負數呢?是由於0個字節可尋址其實能夠繼續分爲多種狀況,譬如:
對全部0個字節可尋址的normal memory region的訪問都是非法的,ASAN將會報錯。而根據其shadow memory的值即可以具體判斷是哪種錯。
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 (實際上Heap right 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
Shadow gap: cc
複製代碼
ShadowAddr = (Addr >> 3) + Offset;
k = *ShadowAddr;
if (k != 0 && ((Addr & 7) + AccessSize > k))
ReportAndCrash(Addr);
複製代碼
在每次內存訪問時,都會執行如上的僞代碼,以判斷這次內存訪問是否合規。
首先根據normal memory的地址找到對應shadow memory的地址,而後取出其中存取的byte值:k。
當這次內存訪問可能會訪問到不可尋址的字節時,ASAN會報錯並結合shadow memory中具體的值明確錯誤類型。
想要檢測UseAfterFree的錯誤,須要有兩點保證:
測試代碼:
// RUN: clang -O -g -fsanitize=address %t && ./a.out
int main(int argc, char **argv) {
int *array = new int[100];
delete [] array;
return array[argc]; // BOOM
}
複製代碼
ASAN輸出的錯誤信息:
=================================================================
==6254== ERROR: AddressSanitizer: heap-use-after-free on address 0x603e0001fc64 at pc 0x417f6a bp 0x7fff626b3250 sp 0x7fff626b3248
READ of size 4 at 0x603e0001fc64 thread T0
#0 0x417f69 in main example_UseAfterFree.cc:5
#1 0x7fae62b5076c (/lib/x86_64-linux-gnu/libc.so.6+0x2176c)
#2 0x417e54 (a.out+0x417e54)
0x603e0001fc64 is located 4 bytes inside of 400-byte region [0x603e0001fc60,0x603e0001fdf0)
freed by thread T0 here:
#0 0x40d4d2 in operator delete[](void*) /home/kcc/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:61
#1 0x417f2e in main example_UseAfterFree.cc:4
previously allocated by thread T0 here:
#0 0x40d312 in operator new[](unsigned long) /home/kcc/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cc:46
#1 0x417f1e in main example_UseAfterFree.cc:3
Shadow bytes around the buggy address:
0x1c07c0003f30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c07c0003f40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c07c0003f50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c07c0003f60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c07c0003f70: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x1c07c0003f80: fa fa fa fa fa fa fa fa fa fa fa fa[fd]fd fd fd
0x1c07c0003f90: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x1c07c0003fa0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x1c07c0003fb0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
0x1c07c0003fc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x1c07c0003fd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
複製代碼
能夠看到,=>指向的那行有一個byte數值用中括號給圈出來了:[fd]。它表示的是這次出錯的內存地址對應的shadow memory的值。而其以前的fa表示Heap left redzone,它是以前該區域有效時的遺留產物。連續的fd總共有50個,每個shadow memory的byte和8個normal memory byte對應,因此能夠知道這次free的內存總共是50×8=400bytes。這一點在上面的log中也獲得了驗證,截取出來展現以下:
0x603e0001fc64 is located 4 bytes inside of 400-byte region [0x603e0001fc60,0x603e0001fdf0)
複製代碼
此外,ASAN的log中不只有出錯時的堆棧信息,還有該內存區域以前free時的堆棧信息。所以咱們能夠清楚地知道該區域是如何被釋放的,從而快速定位問題,解決問題。
想要檢測HeapBufferOverflow的問題,只須要保證一點:
測試代碼:
ASAN輸出的錯誤信息:
=================================================================
==1405==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x0060bef84165 at pc 0x0058714bfb24 bp 0x007fdff09590 sp 0x007fdff09588
WRITE of size 1 at 0x0060bef84165 thread T0
#0 0x58714bfb20 (/system/bin/bootanimation+0x8b20)
#1 0x7b434cd994 (/apex/com.android.runtime/lib64/bionic/libc.so+0x7e994)
0x0060bef84165 is located 1 bytes to the right of 100-byte region [0x0060bef84100,0x0060bef84164)
allocated by thread T0 here:
#0 0x7b4250a1a4 (/system/lib64/libclang_rt.asan-aarch64-android.so+0xc31a4)
#1 0x58714bfac8 (/system/bin/bootanimation+0x8ac8)
#2 0x7b434cd994 (/apex/com.android.runtime/lib64/bionic/libc.so+0x7e994)
#3 0x58714bb04c (/system/bin/bootanimation+0x404c)
#4 0x7b45361b04 (/system/bin/bootanimation+0x54b04)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/system/bin/bootanimation+0x8b20)
Shadow bytes around the buggy address:
0x001c17df07d0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x001c17df07e0: fd fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa
0x001c17df07f0: fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa
0x001c17df0800: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
0x001c17df0810: fd fd fd fd fd fa fa fa fa fa fa fa fa fa fa fa
=>0x001c17df0820: 00 00 00 00 00 00 00 00 00 00 00 00[04]fa fa fa
0x001c17df0830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001c17df0840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001c17df0850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001c17df0860: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x001c17df0870: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
複製代碼
能夠看到最終出錯的shadow memory值爲0x4,表示該shadow memroy對應的normal memory中只有前4個bytes是可尋址的。0x4的shadow memory前還有12個0x0,表示其前面的12個memory region(每一個region有8個byte)都是徹底可尋址的。所以全部可尋址的大小=12×8+4=100,正是代碼中malloc的size。之因此這次訪問會出錯,是由於地址0x60bef84165意圖訪問最後一個region的第五個byte,而該region只有前四個byte可尋址。因爲0x4後面是0xfa,所以這次錯誤屬於HeapBufferOverflow。
自從2011年誕生以來,ASAN已經成功地參與了衆多大型項目,譬如Chrome和Android。雖然它的表現很突出,但仍然有些地方不盡如人意,重點表如今如下幾點:
HWASAN是ASAN工具的「升級版」,它基本上解決了上面所說的ASAN的3個問題。可是它須要64位硬件的支持,也就是說在32位的機器上該工具沒法運行。
AArch64是64位的架構,指的是寄存器的寬度是64位,但並不表示內存的尋址範圍是2^64。真實的尋址範圍和處理器內部的總線寬度有關,實際上ARMv8尋址只用到了低48位。也就是說,一個64bit的指針值,其中真正用於尋址的只有低48位。那麼剩下的高16位幹什麼用呢?答案是隨意發揮。AArch64擁有地址標記(Address tagging, or top-byte-ignore)的特性,它表示容許軟件使用64bit指針值的高8位開發特定功能。
HWASAN用這8bit來存儲一塊內存區域的標籤(tag)。接下來咱們以堆內存示例,展現這8bit到底如何起做用。
堆內存經過malloc分配出來,HWASAN在它返回地址時會更改該有效地址的高8位。更改的值是一個隨機生成的單字節值,譬如0xaf。此外,該分配出來的內存對應的shadow memory值也設爲0xaf。須要注意的是,HWASAN中normal memory和shadow memory的映射關係是16➡1,而ASAN中兩者的映射關係是8➡1。
如下分別討論UseAfterFree和HeapOverFlow的狀況。
當一個堆內存被分配出來時,返回給用戶空間的地址便已經帶上了標籤(存儲於地址的高8位)。以後經過該地址進行內存訪問,將先檢測地址中的標籤值和訪問地址對應的shadow memory的值是否相等。若是相等則驗證經過,能夠進行正常的內存訪問。
當該內存被free時,HWASAN會爲該塊區域分配一個新的隨機值,存儲於其對應的shadow memory中。若是此後再有新的訪問,則地址中的標籤值必然不等於shadow memory中存儲的新的隨機值,所以會有錯誤產生。經過以下圖示能夠很好地明白這一點(圖中只用了4bit記錄標記值,但不影響理解,8bit標記值的檢測和它一致)。
想要檢測HeapOverFlow,有一個前提須要知足:相鄰的memory區域須要有不一樣的shadow memory值,不然將沒法分辨兩個不一樣的memory區域。爲每一個memory區域隨機分配將有機率讓兩個相鄰區域具備一樣的shadow memory值,雖然機率比較小,但總歸是個缺陷。所以工具中會有其餘邏輯保證這個前提。
下圖展現了HeapOverFlow的檢測過程。指針p的標籤和訪問的地址p[32]所對應的shadow memory值不一致,所以報錯(圖中只用了4bit記錄標記值,但不影響理解,8bit標記值的檢測和它一致)。
Abort message: '==12528==ERROR: HWAddressSanitizer: tag-mismatch on address 0x003d557e2c20 at pc 0x00748b4a6918 READ of size 4 at 0x003d557e2c20 tags: d1/9b (ptr/mem) in thread T0 #0 0x748b4a6914 (/system/lib64/libutils.so+0x11914) #1 0x748a521bdc (/apex/com.android.runtime/lib64/bionic/libc.so+0x121bdc) #2 0x748a51ad7c (/apex/com.android.runtime/lib64/bionic/libc.so+0x11ad7c) #3 0x748a47f830 (/apex/com.android.runtime/lib64/bionic/libc.so+0x7f830) [0x003d557e2c20,0x003d557e2c80) is a small unallocated heap chunk; size: 96 offset: 0 Thread: T0 0x006b00002000 stack: [0x007fcd371000,0x007fcdb71000) sz: 8388608 tls: [0x000000000000,0x000000000000) HWAddressSanitizer can not describe address in more detail. Memory tags around the buggy address (one tag corresponds to 16 bytes): e1 e1 e1 e1 83 83 83 83 83 00 a3 a3 a3 a3 a3 a3 b7 b7 b7 b7 b7 00 01 01 01 01 01 00 95 95 95 95 95 00 ec ec ec ec ec 00 c8 c8 c8 c8 c8 00 21 21 21 21 21 00 cb cb cb cb cb 00 b8 b8 b8 b8 b8 00 14 14 14 14 14 14 b9 b9 b9 b9 b9 b9 89 89 89 89 89 89 95 95 95 95 95 95 47 47 47 47 47 00 fe fe fe fe fe 00 c5 c5 c5 c5 c5 00 8e 8e 8e 8e 8e 8e 5c 5c 5c 5c 5c 5c af af af af af af b0 b0 b0 b0 => b0 b0 [9b] 9b 9b 9b 9b 9b 1f 1f 1f 1f 1f 1f 69 69 <= 69 69 69 a0 7a 7a 7a 7a 7a ff eb eb eb eb eb eb 16 16 16 16 16 16 81 81 81 81 81 81 7f 7f 7f 7f 7f 7f 57 57 57 57 57 57 e0 e0 e0 e0 e0 e0 94 94 94 94 94 00 35 35 35 35 35 35 98 98 98 98 98 00 7d 7d 7d 7d 7d 7d 6e 6e 6e 6e 6e 6e 59 59 59 59 59 59 8e 8e 8e 8e 8e 8e 6d 6d 6d 6d 6d 6d 69 69 69 69 69 69 d5 d5 d5 d5 d5 d5 63 63 63 63 63 63 複製代碼
0x9b總共有6個,所以該memory區域的總長爲6×16=96,與上述提示一致。
[0x003d557e2c20,0x003d557e2c80) is a small unallocated heap chunk; size: 96
複製代碼
和ASAN相比,HWASAN具備以下缺點:
不過相對於這些缺點,HWASAN所擁有的優勢更加引人注目:
上述的討論其實迴避了一個問題:若是一個16字節的memory region中只有前幾個字節可尋址(假設是5),那麼其對應的shadow memory值也是5。這時,若是用地址去訪問該region的第2個字節,那麼如何判斷訪問是否合規呢?
此時直接對比地址的tag和shadow memory的值確定是不行的,由於此時的shadow memory值含義發生了變化,它再也不是一個相似於tag的隨機值,而是memory region中可訪問字節的數目。
爲了解決這個難題,HWASAN在這種狀況下將memory region的隨機值保存在最後一個字節中。因此即使地址的tag和shadow memory的值不等,但只要和memory region中最後一個字節相等,也代表該訪問合法。
具體可參考連接:clang.llvm.org/docs/Hardwa…
參考文章: